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.res.ColorStateList;
     27 import android.content.res.Resources;
     28 import android.content.res.TypedArray;
     29 import android.content.res.XmlResourceParser;
     30 import android.graphics.Canvas;
     31 import android.graphics.Paint;
     32 import android.graphics.Path;
     33 import android.graphics.Rect;
     34 import android.graphics.RectF;
     35 import android.graphics.Typeface;
     36 import android.graphics.drawable.Drawable;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.Message;
     40 import android.os.Parcel;
     41 import android.os.Parcelable;
     42 import android.os.ResultReceiver;
     43 import android.os.SystemClock;
     44 import android.text.BoringLayout;
     45 import android.text.ClipboardManager;
     46 import android.text.DynamicLayout;
     47 import android.text.Editable;
     48 import android.text.GetChars;
     49 import android.text.GraphicsOperations;
     50 import android.text.InputFilter;
     51 import android.text.InputType;
     52 import android.text.Layout;
     53 import android.text.ParcelableSpan;
     54 import android.text.Selection;
     55 import android.text.SpanWatcher;
     56 import android.text.Spannable;
     57 import android.text.SpannableString;
     58 import android.text.Spanned;
     59 import android.text.SpannedString;
     60 import android.text.StaticLayout;
     61 import android.text.TextPaint;
     62 import android.text.TextUtils;
     63 import android.text.TextWatcher;
     64 import android.text.method.DateKeyListener;
     65 import android.text.method.DateTimeKeyListener;
     66 import android.text.method.DialerKeyListener;
     67 import android.text.method.DigitsKeyListener;
     68 import android.text.method.KeyListener;
     69 import android.text.method.LinkMovementMethod;
     70 import android.text.method.MetaKeyKeyListener;
     71 import android.text.method.MovementMethod;
     72 import android.text.method.PasswordTransformationMethod;
     73 import android.text.method.SingleLineTransformationMethod;
     74 import android.text.method.TextKeyListener;
     75 import android.text.method.TimeKeyListener;
     76 import android.text.method.TransformationMethod;
     77 import android.text.style.ParagraphStyle;
     78 import android.text.style.URLSpan;
     79 import android.text.style.UpdateAppearance;
     80 import android.text.util.Linkify;
     81 import android.util.AttributeSet;
     82 import android.util.FloatMath;
     83 import android.util.Log;
     84 import android.util.TypedValue;
     85 import android.view.ContextMenu;
     86 import android.view.Gravity;
     87 import android.view.KeyEvent;
     88 import android.view.LayoutInflater;
     89 import android.view.MenuItem;
     90 import android.view.MotionEvent;
     91 import android.view.View;
     92 import android.view.ViewDebug;
     93 import android.view.ViewRoot;
     94 import android.view.ViewTreeObserver;
     95 import android.view.ViewGroup.LayoutParams;
     96 import android.view.accessibility.AccessibilityEvent;
     97 import android.view.accessibility.AccessibilityManager;
     98 import android.view.animation.AnimationUtils;
     99 import android.view.inputmethod.BaseInputConnection;
    100 import android.view.inputmethod.CompletionInfo;
    101 import android.view.inputmethod.EditorInfo;
    102 import android.view.inputmethod.ExtractedText;
    103 import android.view.inputmethod.ExtractedTextRequest;
    104 import android.view.inputmethod.InputConnection;
    105 import android.view.inputmethod.InputMethodManager;
    106 import android.widget.RemoteViews.RemoteView;
    107 
    108 import java.io.IOException;
    109 import java.lang.ref.WeakReference;
    110 import java.util.ArrayList;
    111 
    112 /**
    113  * Displays text to the user and optionally allows them to edit it.  A TextView
    114  * is a complete text editor, however the basic class is configured to not
    115  * allow editing; see {@link EditText} for a subclass that configures the text
    116  * view for editing.
    117  *
    118  * <p>
    119  * <b>XML attributes</b>
    120  * <p>
    121  * See {@link android.R.styleable#TextView TextView Attributes},
    122  * {@link android.R.styleable#View View Attributes}
    123  *
    124  * @attr ref android.R.styleable#TextView_text
    125  * @attr ref android.R.styleable#TextView_bufferType
    126  * @attr ref android.R.styleable#TextView_hint
    127  * @attr ref android.R.styleable#TextView_textColor
    128  * @attr ref android.R.styleable#TextView_textColorHighlight
    129  * @attr ref android.R.styleable#TextView_textColorHint
    130  * @attr ref android.R.styleable#TextView_textAppearance
    131  * @attr ref android.R.styleable#TextView_textColorLink
    132  * @attr ref android.R.styleable#TextView_textSize
    133  * @attr ref android.R.styleable#TextView_textScaleX
    134  * @attr ref android.R.styleable#TextView_typeface
    135  * @attr ref android.R.styleable#TextView_textStyle
    136  * @attr ref android.R.styleable#TextView_cursorVisible
    137  * @attr ref android.R.styleable#TextView_maxLines
    138  * @attr ref android.R.styleable#TextView_maxHeight
    139  * @attr ref android.R.styleable#TextView_lines
    140  * @attr ref android.R.styleable#TextView_height
    141  * @attr ref android.R.styleable#TextView_minLines
    142  * @attr ref android.R.styleable#TextView_minHeight
    143  * @attr ref android.R.styleable#TextView_maxEms
    144  * @attr ref android.R.styleable#TextView_maxWidth
    145  * @attr ref android.R.styleable#TextView_ems
    146  * @attr ref android.R.styleable#TextView_width
    147  * @attr ref android.R.styleable#TextView_minEms
    148  * @attr ref android.R.styleable#TextView_minWidth
    149  * @attr ref android.R.styleable#TextView_gravity
    150  * @attr ref android.R.styleable#TextView_scrollHorizontally
    151  * @attr ref android.R.styleable#TextView_password
    152  * @attr ref android.R.styleable#TextView_singleLine
    153  * @attr ref android.R.styleable#TextView_selectAllOnFocus
    154  * @attr ref android.R.styleable#TextView_includeFontPadding
    155  * @attr ref android.R.styleable#TextView_maxLength
    156  * @attr ref android.R.styleable#TextView_shadowColor
    157  * @attr ref android.R.styleable#TextView_shadowDx
    158  * @attr ref android.R.styleable#TextView_shadowDy
    159  * @attr ref android.R.styleable#TextView_shadowRadius
    160  * @attr ref android.R.styleable#TextView_autoLink
    161  * @attr ref android.R.styleable#TextView_linksClickable
    162  * @attr ref android.R.styleable#TextView_numeric
    163  * @attr ref android.R.styleable#TextView_digits
    164  * @attr ref android.R.styleable#TextView_phoneNumber
    165  * @attr ref android.R.styleable#TextView_inputMethod
    166  * @attr ref android.R.styleable#TextView_capitalize
    167  * @attr ref android.R.styleable#TextView_autoText
    168  * @attr ref android.R.styleable#TextView_editable
    169  * @attr ref android.R.styleable#TextView_freezesText
    170  * @attr ref android.R.styleable#TextView_ellipsize
    171  * @attr ref android.R.styleable#TextView_drawableTop
    172  * @attr ref android.R.styleable#TextView_drawableBottom
    173  * @attr ref android.R.styleable#TextView_drawableRight
    174  * @attr ref android.R.styleable#TextView_drawableLeft
    175  * @attr ref android.R.styleable#TextView_drawablePadding
    176  * @attr ref android.R.styleable#TextView_lineSpacingExtra
    177  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
    178  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
    179  * @attr ref android.R.styleable#TextView_inputType
    180  * @attr ref android.R.styleable#TextView_imeOptions
    181  * @attr ref android.R.styleable#TextView_privateImeOptions
    182  * @attr ref android.R.styleable#TextView_imeActionLabel
    183  * @attr ref android.R.styleable#TextView_imeActionId
    184  * @attr ref android.R.styleable#TextView_editorExtras
    185  */
    186 @RemoteView
    187 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
    188     static final String TAG = "TextView";
    189     static final boolean DEBUG_EXTRACT = false;
    190 
    191     private static int PRIORITY = 100;
    192 
    193     private ColorStateList mTextColor;
    194     private int mCurTextColor;
    195     private ColorStateList mHintTextColor;
    196     private ColorStateList mLinkTextColor;
    197     private int mCurHintTextColor;
    198     private boolean mFreezesText;
    199     private boolean mFrozenWithFocus;
    200     private boolean mTemporaryDetach;
    201     private boolean mDispatchTemporaryDetach;
    202 
    203     private boolean mEatTouchRelease = false;
    204     private boolean mScrolled = false;
    205 
    206     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
    207     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
    208 
    209     private float mShadowRadius, mShadowDx, mShadowDy;
    210 
    211     private static final int PREDRAW_NOT_REGISTERED = 0;
    212     private static final int PREDRAW_PENDING = 1;
    213     private static final int PREDRAW_DONE = 2;
    214     private int mPreDrawState = PREDRAW_NOT_REGISTERED;
    215 
    216     private TextUtils.TruncateAt mEllipsize = null;
    217 
    218     // Enum for the "typeface" XML parameter.
    219     // TODO: How can we get this from the XML instead of hardcoding it here?
    220     private static final int SANS = 1;
    221     private static final int SERIF = 2;
    222     private static final int MONOSPACE = 3;
    223 
    224     // Bitfield for the "numeric" XML parameter.
    225     // TODO: How can we get this from the XML instead of hardcoding it here?
    226     private static final int SIGNED = 2;
    227     private static final int DECIMAL = 4;
    228 
    229     class Drawables {
    230         final Rect mCompoundRect = new Rect();
    231         Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
    232         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
    233         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
    234         int mDrawablePadding;
    235     }
    236     private Drawables mDrawables;
    237 
    238     private CharSequence mError;
    239     private boolean mErrorWasChanged;
    240     private ErrorPopup mPopup;
    241     /**
    242      * This flag is set if the TextView tries to display an error before it
    243      * is attached to the window (so its position is still unknown).
    244      * It causes the error to be shown later, when onAttachedToWindow()
    245      * is called.
    246      */
    247     private boolean mShowErrorAfterAttach;
    248 
    249     private CharWrapper mCharWrapper = null;
    250 
    251     private boolean mSelectionMoved = false;
    252     private boolean mTouchFocusSelected = false;
    253 
    254     private Marquee mMarquee;
    255     private boolean mRestartMarquee;
    256 
    257     private int mMarqueeRepeatLimit = 3;
    258 
    259     class InputContentType {
    260         int imeOptions = EditorInfo.IME_NULL;
    261         String privateImeOptions;
    262         CharSequence imeActionLabel;
    263         int imeActionId;
    264         Bundle extras;
    265         OnEditorActionListener onEditorActionListener;
    266         boolean enterDown;
    267     }
    268     InputContentType mInputContentType;
    269 
    270     class InputMethodState {
    271         Rect mCursorRectInWindow = new Rect();
    272         RectF mTmpRectF = new RectF();
    273         float[] mTmpOffset = new float[2];
    274         ExtractedTextRequest mExtracting;
    275         final ExtractedText mTmpExtracted = new ExtractedText();
    276         int mBatchEditNesting;
    277         boolean mCursorChanged;
    278         boolean mSelectionModeChanged;
    279         boolean mContentChanged;
    280         int mChangedStart, mChangedEnd, mChangedDelta;
    281     }
    282     InputMethodState mInputMethodState;
    283 
    284     /*
    285      * Kick-start the font cache for the zygote process (to pay the cost of
    286      * initializing freetype for our default font only once).
    287      */
    288     static {
    289         Paint p = new Paint();
    290         p.setAntiAlias(true);
    291         // We don't care about the result, just the side-effect of measuring.
    292         p.measureText("H");
    293     }
    294 
    295     /**
    296      * Interface definition for a callback to be invoked when an action is
    297      * performed on the editor.
    298      */
    299     public interface OnEditorActionListener {
    300         /**
    301          * Called when an action is being performed.
    302          *
    303          * @param v The view that was clicked.
    304          * @param actionId Identifier of the action.  This will be either the
    305          * identifier you supplied, or {@link EditorInfo#IME_NULL
    306          * EditorInfo.IME_NULL} if being called due to the enter key
    307          * being pressed.
    308          * @param event If triggered by an enter key, this is the event;
    309          * otherwise, this is null.
    310          * @return Return true if you have consumed the action, else false.
    311          */
    312         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
    313     }
    314 
    315     public TextView(Context context) {
    316         this(context, null);
    317     }
    318 
    319     public TextView(Context context,
    320                     AttributeSet attrs) {
    321         this(context, attrs, com.android.internal.R.attr.textViewStyle);
    322     }
    323 
    324     public TextView(Context context,
    325                     AttributeSet attrs,
    326                     int defStyle) {
    327         super(context, attrs, defStyle);
    328         mText = "";
    329 
    330         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    331         mTextPaint.density = getResources().getDisplayMetrics().density;
    332         mTextPaint.setCompatibilityScaling(
    333                 getResources().getCompatibilityInfo().applicationScale);
    334 
    335         // If we get the paint from the skin, we should set it to left, since
    336         // the layout always wants it to be left.
    337         // mTextPaint.setTextAlign(Paint.Align.LEFT);
    338 
    339         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    340         mHighlightPaint.setCompatibilityScaling(
    341                 getResources().getCompatibilityInfo().applicationScale);
    342 
    343         mMovement = getDefaultMovementMethod();
    344         mTransformation = null;
    345 
    346         TypedArray a =
    347             context.obtainStyledAttributes(
    348                 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
    349 
    350         int textColorHighlight = 0;
    351         ColorStateList textColor = null;
    352         ColorStateList textColorHint = null;
    353         ColorStateList textColorLink = null;
    354         int textSize = 15;
    355         int typefaceIndex = -1;
    356         int styleIndex = -1;
    357 
    358         /*
    359          * Look the appearance up without checking first if it exists because
    360          * almost every TextView has one and it greatly simplifies the logic
    361          * to be able to parse the appearance first and then let specific tags
    362          * for this View override it.
    363          */
    364         TypedArray appearance = null;
    365         int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
    366         if (ap != -1) {
    367             appearance = context.obtainStyledAttributes(ap,
    368                                 com.android.internal.R.styleable.
    369                                 TextAppearance);
    370         }
    371         if (appearance != null) {
    372             int n = appearance.getIndexCount();
    373             for (int i = 0; i < n; i++) {
    374                 int attr = appearance.getIndex(i);
    375 
    376                 switch (attr) {
    377                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
    378                     textColorHighlight = appearance.getColor(attr, textColorHighlight);
    379                     break;
    380 
    381                 case com.android.internal.R.styleable.TextAppearance_textColor:
    382                     textColor = appearance.getColorStateList(attr);
    383                     break;
    384 
    385                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
    386                     textColorHint = appearance.getColorStateList(attr);
    387                     break;
    388 
    389                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
    390                     textColorLink = appearance.getColorStateList(attr);
    391                     break;
    392 
    393                 case com.android.internal.R.styleable.TextAppearance_textSize:
    394                     textSize = appearance.getDimensionPixelSize(attr, textSize);
    395                     break;
    396 
    397                 case com.android.internal.R.styleable.TextAppearance_typeface:
    398                     typefaceIndex = appearance.getInt(attr, -1);
    399                     break;
    400 
    401                 case com.android.internal.R.styleable.TextAppearance_textStyle:
    402                     styleIndex = appearance.getInt(attr, -1);
    403                     break;
    404                 }
    405             }
    406 
    407             appearance.recycle();
    408         }
    409 
    410         boolean editable = getDefaultEditable();
    411         CharSequence inputMethod = null;
    412         int numeric = 0;
    413         CharSequence digits = null;
    414         boolean phone = false;
    415         boolean autotext = false;
    416         int autocap = -1;
    417         int buffertype = 0;
    418         boolean selectallonfocus = false;
    419         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
    420             drawableBottom = null;
    421         int drawablePadding = 0;
    422         int ellipsize = -1;
    423         boolean singleLine = false;
    424         int maxlength = -1;
    425         CharSequence text = "";
    426         CharSequence hint = null;
    427         int shadowcolor = 0;
    428         float dx = 0, dy = 0, r = 0;
    429         boolean password = false;
    430         int inputType = EditorInfo.TYPE_NULL;
    431 
    432         int n = a.getIndexCount();
    433         for (int i = 0; i < n; i++) {
    434             int attr = a.getIndex(i);
    435 
    436             switch (attr) {
    437             case com.android.internal.R.styleable.TextView_editable:
    438                 editable = a.getBoolean(attr, editable);
    439                 break;
    440 
    441             case com.android.internal.R.styleable.TextView_inputMethod:
    442                 inputMethod = a.getText(attr);
    443                 break;
    444 
    445             case com.android.internal.R.styleable.TextView_numeric:
    446                 numeric = a.getInt(attr, numeric);
    447                 break;
    448 
    449             case com.android.internal.R.styleable.TextView_digits:
    450                 digits = a.getText(attr);
    451                 break;
    452 
    453             case com.android.internal.R.styleable.TextView_phoneNumber:
    454                 phone = a.getBoolean(attr, phone);
    455                 break;
    456 
    457             case com.android.internal.R.styleable.TextView_autoText:
    458                 autotext = a.getBoolean(attr, autotext);
    459                 break;
    460 
    461             case com.android.internal.R.styleable.TextView_capitalize:
    462                 autocap = a.getInt(attr, autocap);
    463                 break;
    464 
    465             case com.android.internal.R.styleable.TextView_bufferType:
    466                 buffertype = a.getInt(attr, buffertype);
    467                 break;
    468 
    469             case com.android.internal.R.styleable.TextView_selectAllOnFocus:
    470                 selectallonfocus = a.getBoolean(attr, selectallonfocus);
    471                 break;
    472 
    473             case com.android.internal.R.styleable.TextView_autoLink:
    474                 mAutoLinkMask = a.getInt(attr, 0);
    475                 break;
    476 
    477             case com.android.internal.R.styleable.TextView_linksClickable:
    478                 mLinksClickable = a.getBoolean(attr, true);
    479                 break;
    480 
    481             case com.android.internal.R.styleable.TextView_drawableLeft:
    482                 drawableLeft = a.getDrawable(attr);
    483                 break;
    484 
    485             case com.android.internal.R.styleable.TextView_drawableTop:
    486                 drawableTop = a.getDrawable(attr);
    487                 break;
    488 
    489             case com.android.internal.R.styleable.TextView_drawableRight:
    490                 drawableRight = a.getDrawable(attr);
    491                 break;
    492 
    493             case com.android.internal.R.styleable.TextView_drawableBottom:
    494                 drawableBottom = a.getDrawable(attr);
    495                 break;
    496 
    497             case com.android.internal.R.styleable.TextView_drawablePadding:
    498                 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
    499                 break;
    500 
    501             case com.android.internal.R.styleable.TextView_maxLines:
    502                 setMaxLines(a.getInt(attr, -1));
    503                 break;
    504 
    505             case com.android.internal.R.styleable.TextView_maxHeight:
    506                 setMaxHeight(a.getDimensionPixelSize(attr, -1));
    507                 break;
    508 
    509             case com.android.internal.R.styleable.TextView_lines:
    510                 setLines(a.getInt(attr, -1));
    511                 break;
    512 
    513             case com.android.internal.R.styleable.TextView_height:
    514                 setHeight(a.getDimensionPixelSize(attr, -1));
    515                 break;
    516 
    517             case com.android.internal.R.styleable.TextView_minLines:
    518                 setMinLines(a.getInt(attr, -1));
    519                 break;
    520 
    521             case com.android.internal.R.styleable.TextView_minHeight:
    522                 setMinHeight(a.getDimensionPixelSize(attr, -1));
    523                 break;
    524 
    525             case com.android.internal.R.styleable.TextView_maxEms:
    526                 setMaxEms(a.getInt(attr, -1));
    527                 break;
    528 
    529             case com.android.internal.R.styleable.TextView_maxWidth:
    530                 setMaxWidth(a.getDimensionPixelSize(attr, -1));
    531                 break;
    532 
    533             case com.android.internal.R.styleable.TextView_ems:
    534                 setEms(a.getInt(attr, -1));
    535                 break;
    536 
    537             case com.android.internal.R.styleable.TextView_width:
    538                 setWidth(a.getDimensionPixelSize(attr, -1));
    539                 break;
    540 
    541             case com.android.internal.R.styleable.TextView_minEms:
    542                 setMinEms(a.getInt(attr, -1));
    543                 break;
    544 
    545             case com.android.internal.R.styleable.TextView_minWidth:
    546                 setMinWidth(a.getDimensionPixelSize(attr, -1));
    547                 break;
    548 
    549             case com.android.internal.R.styleable.TextView_gravity:
    550                 setGravity(a.getInt(attr, -1));
    551                 break;
    552 
    553             case com.android.internal.R.styleable.TextView_hint:
    554                 hint = a.getText(attr);
    555                 break;
    556 
    557             case com.android.internal.R.styleable.TextView_text:
    558                 text = a.getText(attr);
    559                 break;
    560 
    561             case com.android.internal.R.styleable.TextView_scrollHorizontally:
    562                 if (a.getBoolean(attr, false)) {
    563                     setHorizontallyScrolling(true);
    564                 }
    565                 break;
    566 
    567             case com.android.internal.R.styleable.TextView_singleLine:
    568                 singleLine = a.getBoolean(attr, singleLine);
    569                 break;
    570 
    571             case com.android.internal.R.styleable.TextView_ellipsize:
    572                 ellipsize = a.getInt(attr, ellipsize);
    573                 break;
    574 
    575             case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
    576                 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
    577                 break;
    578 
    579             case com.android.internal.R.styleable.TextView_includeFontPadding:
    580                 if (!a.getBoolean(attr, true)) {
    581                     setIncludeFontPadding(false);
    582                 }
    583                 break;
    584 
    585             case com.android.internal.R.styleable.TextView_cursorVisible:
    586                 if (!a.getBoolean(attr, true)) {
    587                     setCursorVisible(false);
    588                 }
    589                 break;
    590 
    591             case com.android.internal.R.styleable.TextView_maxLength:
    592                 maxlength = a.getInt(attr, -1);
    593                 break;
    594 
    595             case com.android.internal.R.styleable.TextView_textScaleX:
    596                 setTextScaleX(a.getFloat(attr, 1.0f));
    597                 break;
    598 
    599             case com.android.internal.R.styleable.TextView_freezesText:
    600                 mFreezesText = a.getBoolean(attr, false);
    601                 break;
    602 
    603             case com.android.internal.R.styleable.TextView_shadowColor:
    604                 shadowcolor = a.getInt(attr, 0);
    605                 break;
    606 
    607             case com.android.internal.R.styleable.TextView_shadowDx:
    608                 dx = a.getFloat(attr, 0);
    609                 break;
    610 
    611             case com.android.internal.R.styleable.TextView_shadowDy:
    612                 dy = a.getFloat(attr, 0);
    613                 break;
    614 
    615             case com.android.internal.R.styleable.TextView_shadowRadius:
    616                 r = a.getFloat(attr, 0);
    617                 break;
    618 
    619             case com.android.internal.R.styleable.TextView_enabled:
    620                 setEnabled(a.getBoolean(attr, isEnabled()));
    621                 break;
    622 
    623             case com.android.internal.R.styleable.TextView_textColorHighlight:
    624                 textColorHighlight = a.getColor(attr, textColorHighlight);
    625                 break;
    626 
    627             case com.android.internal.R.styleable.TextView_textColor:
    628                 textColor = a.getColorStateList(attr);
    629                 break;
    630 
    631             case com.android.internal.R.styleable.TextView_textColorHint:
    632                 textColorHint = a.getColorStateList(attr);
    633                 break;
    634 
    635             case com.android.internal.R.styleable.TextView_textColorLink:
    636                 textColorLink = a.getColorStateList(attr);
    637                 break;
    638 
    639             case com.android.internal.R.styleable.TextView_textSize:
    640                 textSize = a.getDimensionPixelSize(attr, textSize);
    641                 break;
    642 
    643             case com.android.internal.R.styleable.TextView_typeface:
    644                 typefaceIndex = a.getInt(attr, typefaceIndex);
    645                 break;
    646 
    647             case com.android.internal.R.styleable.TextView_textStyle:
    648                 styleIndex = a.getInt(attr, styleIndex);
    649                 break;
    650 
    651             case com.android.internal.R.styleable.TextView_password:
    652                 password = a.getBoolean(attr, password);
    653                 break;
    654 
    655             case com.android.internal.R.styleable.TextView_lineSpacingExtra:
    656                 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
    657                 break;
    658 
    659             case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
    660                 mSpacingMult = a.getFloat(attr, mSpacingMult);
    661                 break;
    662 
    663             case com.android.internal.R.styleable.TextView_inputType:
    664                 inputType = a.getInt(attr, mInputType);
    665                 break;
    666 
    667             case com.android.internal.R.styleable.TextView_imeOptions:
    668                 if (mInputContentType == null) {
    669                     mInputContentType = new InputContentType();
    670                 }
    671                 mInputContentType.imeOptions = a.getInt(attr,
    672                         mInputContentType.imeOptions);
    673                 break;
    674 
    675             case com.android.internal.R.styleable.TextView_imeActionLabel:
    676                 if (mInputContentType == null) {
    677                     mInputContentType = new InputContentType();
    678                 }
    679                 mInputContentType.imeActionLabel = a.getText(attr);
    680                 break;
    681 
    682             case com.android.internal.R.styleable.TextView_imeActionId:
    683                 if (mInputContentType == null) {
    684                     mInputContentType = new InputContentType();
    685                 }
    686                 mInputContentType.imeActionId = a.getInt(attr,
    687                         mInputContentType.imeActionId);
    688                 break;
    689 
    690             case com.android.internal.R.styleable.TextView_privateImeOptions:
    691                 setPrivateImeOptions(a.getString(attr));
    692                 break;
    693 
    694             case com.android.internal.R.styleable.TextView_editorExtras:
    695                 try {
    696                     setInputExtras(a.getResourceId(attr, 0));
    697                 } catch (XmlPullParserException e) {
    698                     Log.w("TextView", "Failure reading input extras", e);
    699                 } catch (IOException e) {
    700                     Log.w("TextView", "Failure reading input extras", e);
    701                 }
    702                 break;
    703             }
    704         }
    705         a.recycle();
    706 
    707         BufferType bufferType = BufferType.EDITABLE;
    708 
    709         if ((inputType&(EditorInfo.TYPE_MASK_CLASS
    710                 |EditorInfo.TYPE_MASK_VARIATION))
    711                 == (EditorInfo.TYPE_CLASS_TEXT
    712                         |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
    713             password = true;
    714         }
    715 
    716         if (inputMethod != null) {
    717             Class c;
    718 
    719             try {
    720                 c = Class.forName(inputMethod.toString());
    721             } catch (ClassNotFoundException ex) {
    722                 throw new RuntimeException(ex);
    723             }
    724 
    725             try {
    726                 mInput = (KeyListener) c.newInstance();
    727             } catch (InstantiationException ex) {
    728                 throw new RuntimeException(ex);
    729             } catch (IllegalAccessException ex) {
    730                 throw new RuntimeException(ex);
    731             }
    732             try {
    733                 mInputType = inputType != EditorInfo.TYPE_NULL
    734                         ? inputType
    735                         : mInput.getInputType();
    736             } catch (IncompatibleClassChangeError e) {
    737                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
    738             }
    739         } else if (digits != null) {
    740             mInput = DigitsKeyListener.getInstance(digits.toString());
    741             // If no input type was specified, we will default to generic
    742             // text, since we can't tell the IME about the set of digits
    743             // that was selected.
    744             mInputType = inputType != EditorInfo.TYPE_NULL
    745                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
    746         } else if (inputType != EditorInfo.TYPE_NULL) {
    747             setInputType(inputType, true);
    748             singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
    749                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
    750                     (EditorInfo.TYPE_CLASS_TEXT
    751                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
    752         } else if (phone) {
    753             mInput = DialerKeyListener.getInstance();
    754             mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
    755         } else if (numeric != 0) {
    756             mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
    757                                                    (numeric & DECIMAL) != 0);
    758             inputType = EditorInfo.TYPE_CLASS_NUMBER;
    759             if ((numeric & SIGNED) != 0) {
    760                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
    761             }
    762             if ((numeric & DECIMAL) != 0) {
    763                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
    764             }
    765             mInputType = inputType;
    766         } else if (autotext || autocap != -1) {
    767             TextKeyListener.Capitalize cap;
    768 
    769             inputType = EditorInfo.TYPE_CLASS_TEXT;
    770             if (!singleLine) {
    771                 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
    772             }
    773 
    774             switch (autocap) {
    775             case 1:
    776                 cap = TextKeyListener.Capitalize.SENTENCES;
    777                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
    778                 break;
    779 
    780             case 2:
    781                 cap = TextKeyListener.Capitalize.WORDS;
    782                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
    783                 break;
    784 
    785             case 3:
    786                 cap = TextKeyListener.Capitalize.CHARACTERS;
    787                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
    788                 break;
    789 
    790             default:
    791                 cap = TextKeyListener.Capitalize.NONE;
    792                 break;
    793             }
    794 
    795             mInput = TextKeyListener.getInstance(autotext, cap);
    796             mInputType = inputType;
    797         } else if (editable) {
    798             mInput = TextKeyListener.getInstance();
    799             mInputType = EditorInfo.TYPE_CLASS_TEXT;
    800         } else {
    801             mInput = null;
    802 
    803             switch (buffertype) {
    804                 case 0:
    805                     bufferType = BufferType.NORMAL;
    806                     break;
    807                 case 1:
    808                     bufferType = BufferType.SPANNABLE;
    809                     break;
    810                 case 2:
    811                     bufferType = BufferType.EDITABLE;
    812                     break;
    813             }
    814         }
    815 
    816         if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
    817                 == EditorInfo.TYPE_CLASS_TEXT) {
    818             mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
    819                 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
    820         }
    821 
    822         if (selectallonfocus) {
    823             mSelectAllOnFocus = true;
    824 
    825             if (bufferType == BufferType.NORMAL)
    826                 bufferType = BufferType.SPANNABLE;
    827         }
    828 
    829         setCompoundDrawablesWithIntrinsicBounds(
    830             drawableLeft, drawableTop, drawableRight, drawableBottom);
    831         setCompoundDrawablePadding(drawablePadding);
    832 
    833         if (singleLine) {
    834             setSingleLine();
    835 
    836             if (mInput == null && ellipsize < 0) {
    837                 ellipsize = 3; // END
    838             }
    839         }
    840 
    841         switch (ellipsize) {
    842             case 1:
    843                 setEllipsize(TextUtils.TruncateAt.START);
    844                 break;
    845             case 2:
    846                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
    847                 break;
    848             case 3:
    849                 setEllipsize(TextUtils.TruncateAt.END);
    850                 break;
    851             case 4:
    852                 setHorizontalFadingEdgeEnabled(true);
    853                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
    854                 break;
    855         }
    856 
    857         setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
    858         setHintTextColor(textColorHint);
    859         setLinkTextColor(textColorLink);
    860         if (textColorHighlight != 0) {
    861             setHighlightColor(textColorHighlight);
    862         }
    863         setRawTextSize(textSize);
    864 
    865         if (password) {
    866             setTransformationMethod(PasswordTransformationMethod.getInstance());
    867             typefaceIndex = MONOSPACE;
    868         } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
    869                 |EditorInfo.TYPE_MASK_VARIATION))
    870                 == (EditorInfo.TYPE_CLASS_TEXT
    871                         |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
    872             typefaceIndex = MONOSPACE;
    873         }
    874 
    875         setTypefaceByIndex(typefaceIndex, styleIndex);
    876 
    877         if (shadowcolor != 0) {
    878             setShadowLayer(r, dx, dy, shadowcolor);
    879         }
    880 
    881         if (maxlength >= 0) {
    882             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
    883         } else {
    884             setFilters(NO_FILTERS);
    885         }
    886 
    887         setText(text, bufferType);
    888         if (hint != null) setHint(hint);
    889 
    890         /*
    891          * Views are not normally focusable unless specified to be.
    892          * However, TextViews that have input or movement methods *are*
    893          * focusable by default.
    894          */
    895         a = context.obtainStyledAttributes(attrs,
    896                                            com.android.internal.R.styleable.View,
    897                                            defStyle, 0);
    898 
    899         boolean focusable = mMovement != null || mInput != null;
    900         boolean clickable = focusable;
    901         boolean longClickable = focusable;
    902 
    903         n = a.getIndexCount();
    904         for (int i = 0; i < n; i++) {
    905             int attr = a.getIndex(i);
    906 
    907             switch (attr) {
    908             case com.android.internal.R.styleable.View_focusable:
    909                 focusable = a.getBoolean(attr, focusable);
    910                 break;
    911 
    912             case com.android.internal.R.styleable.View_clickable:
    913                 clickable = a.getBoolean(attr, clickable);
    914                 break;
    915 
    916             case com.android.internal.R.styleable.View_longClickable:
    917                 longClickable = a.getBoolean(attr, longClickable);
    918                 break;
    919             }
    920         }
    921         a.recycle();
    922 
    923         setFocusable(focusable);
    924         setClickable(clickable);
    925         setLongClickable(longClickable);
    926     }
    927 
    928     private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
    929         Typeface tf = null;
    930         switch (typefaceIndex) {
    931             case SANS:
    932                 tf = Typeface.SANS_SERIF;
    933                 break;
    934 
    935             case SERIF:
    936                 tf = Typeface.SERIF;
    937                 break;
    938 
    939             case MONOSPACE:
    940                 tf = Typeface.MONOSPACE;
    941                 break;
    942         }
    943 
    944         setTypeface(tf, styleIndex);
    945     }
    946 
    947     /**
    948      * Sets the typeface and style in which the text should be displayed,
    949      * and turns on the fake bold and italic bits in the Paint if the
    950      * Typeface that you provided does not have all the bits in the
    951      * style that you specified.
    952      *
    953      * @attr ref android.R.styleable#TextView_typeface
    954      * @attr ref android.R.styleable#TextView_textStyle
    955      */
    956     public void setTypeface(Typeface tf, int style) {
    957         if (style > 0) {
    958             if (tf == null) {
    959                 tf = Typeface.defaultFromStyle(style);
    960             } else {
    961                 tf = Typeface.create(tf, style);
    962             }
    963 
    964             setTypeface(tf);
    965             // now compute what (if any) algorithmic styling is needed
    966             int typefaceStyle = tf != null ? tf.getStyle() : 0;
    967             int need = style & ~typefaceStyle;
    968             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
    969             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    970         } else {
    971             mTextPaint.setFakeBoldText(false);
    972             mTextPaint.setTextSkewX(0);
    973             setTypeface(tf);
    974         }
    975     }
    976 
    977     /**
    978      * Subclasses override this to specify that they have a KeyListener
    979      * by default even if not specifically called for in the XML options.
    980      */
    981     protected boolean getDefaultEditable() {
    982         return false;
    983     }
    984 
    985     /**
    986      * Subclasses override this to specify a default movement method.
    987      */
    988     protected MovementMethod getDefaultMovementMethod() {
    989         return null;
    990     }
    991 
    992     /**
    993      * Return the text the TextView is displaying. If setText() was called with
    994      * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
    995      * the return value from this method to Spannable or Editable, respectively.
    996      *
    997      * Note: The content of the return value should not be modified. If you want
    998      * a modifiable one, you should make your own copy first.
    999      */
   1000     @ViewDebug.CapturedViewProperty
   1001     public CharSequence getText() {
   1002         return mText;
   1003     }
   1004 
   1005     /**
   1006      * Returns the length, in characters, of the text managed by this TextView
   1007      */
   1008     public int length() {
   1009         return mText.length();
   1010     }
   1011 
   1012     /**
   1013      * Return the text the TextView is displaying as an Editable object.  If
   1014      * the text is not editable, null is returned.
   1015      *
   1016      * @see #getText
   1017      */
   1018     public Editable getEditableText() {
   1019         return (mText instanceof Editable) ? (Editable)mText : null;
   1020     }
   1021 
   1022     /**
   1023      * @return the height of one standard line in pixels.  Note that markup
   1024      * within the text can cause individual lines to be taller or shorter
   1025      * than this height, and the layout may contain additional first-
   1026      * or last-line padding.
   1027      */
   1028     public int getLineHeight() {
   1029         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
   1030                           + mSpacingAdd);
   1031     }
   1032 
   1033     /**
   1034      * @return the Layout that is currently being used to display the text.
   1035      * This can be null if the text or width has recently changes.
   1036      */
   1037     public final Layout getLayout() {
   1038         return mLayout;
   1039     }
   1040 
   1041     /**
   1042      * @return the current key listener for this TextView.
   1043      * This will frequently be null for non-EditText TextViews.
   1044      */
   1045     public final KeyListener getKeyListener() {
   1046         return mInput;
   1047     }
   1048 
   1049     /**
   1050      * Sets the key listener to be used with this TextView.  This can be null
   1051      * to disallow user input.  Note that this method has significant and
   1052      * subtle interactions with soft keyboards and other input method:
   1053      * see {@link KeyListener#getInputType() KeyListener.getContentType()}
   1054      * for important details.  Calling this method will replace the current
   1055      * content type of the text view with the content type returned by the
   1056      * key listener.
   1057      * <p>
   1058      * Be warned that if you want a TextView with a key listener or movement
   1059      * method not to be focusable, or if you want a TextView without a
   1060      * key listener or movement method to be focusable, you must call
   1061      * {@link #setFocusable} again after calling this to get the focusability
   1062      * back the way you want it.
   1063      *
   1064      * @attr ref android.R.styleable#TextView_numeric
   1065      * @attr ref android.R.styleable#TextView_digits
   1066      * @attr ref android.R.styleable#TextView_phoneNumber
   1067      * @attr ref android.R.styleable#TextView_inputMethod
   1068      * @attr ref android.R.styleable#TextView_capitalize
   1069      * @attr ref android.R.styleable#TextView_autoText
   1070      */
   1071     public void setKeyListener(KeyListener input) {
   1072         setKeyListenerOnly(input);
   1073         fixFocusableAndClickableSettings();
   1074 
   1075         if (input != null) {
   1076             try {
   1077                 mInputType = mInput.getInputType();
   1078             } catch (IncompatibleClassChangeError e) {
   1079                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
   1080             }
   1081             if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
   1082                     == EditorInfo.TYPE_CLASS_TEXT) {
   1083                 if (mSingleLine) {
   1084                     mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   1085                 } else {
   1086                     mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   1087                 }
   1088             }
   1089         } else {
   1090             mInputType = EditorInfo.TYPE_NULL;
   1091         }
   1092 
   1093         InputMethodManager imm = InputMethodManager.peekInstance();
   1094         if (imm != null) imm.restartInput(this);
   1095     }
   1096 
   1097     private void setKeyListenerOnly(KeyListener input) {
   1098         mInput = input;
   1099         if (mInput != null && !(mText instanceof Editable))
   1100             setText(mText);
   1101 
   1102         setFilters((Editable) mText, mFilters);
   1103     }
   1104 
   1105     /**
   1106      * @return the movement method being used for this TextView.
   1107      * This will frequently be null for non-EditText TextViews.
   1108      */
   1109     public final MovementMethod getMovementMethod() {
   1110         return mMovement;
   1111     }
   1112 
   1113     /**
   1114      * Sets the movement method (arrow key handler) to be used for
   1115      * this TextView.  This can be null to disallow using the arrow keys
   1116      * to move the cursor or scroll the view.
   1117      * <p>
   1118      * Be warned that if you want a TextView with a key listener or movement
   1119      * method not to be focusable, or if you want a TextView without a
   1120      * key listener or movement method to be focusable, you must call
   1121      * {@link #setFocusable} again after calling this to get the focusability
   1122      * back the way you want it.
   1123      */
   1124     public final void setMovementMethod(MovementMethod movement) {
   1125         mMovement = movement;
   1126 
   1127         if (mMovement != null && !(mText instanceof Spannable))
   1128             setText(mText);
   1129 
   1130         fixFocusableAndClickableSettings();
   1131     }
   1132 
   1133     private void fixFocusableAndClickableSettings() {
   1134         if ((mMovement != null) || mInput != null) {
   1135             setFocusable(true);
   1136             setClickable(true);
   1137             setLongClickable(true);
   1138         } else {
   1139             setFocusable(false);
   1140             setClickable(false);
   1141             setLongClickable(false);
   1142         }
   1143     }
   1144 
   1145     /**
   1146      * @return the current transformation method for this TextView.
   1147      * This will frequently be null except for single-line and password
   1148      * fields.
   1149      */
   1150     public final TransformationMethod getTransformationMethod() {
   1151         return mTransformation;
   1152     }
   1153 
   1154     /**
   1155      * Sets the transformation that is applied to the text that this
   1156      * TextView is displaying.
   1157      *
   1158      * @attr ref android.R.styleable#TextView_password
   1159      * @attr ref android.R.styleable#TextView_singleLine
   1160      */
   1161     public final void setTransformationMethod(TransformationMethod method) {
   1162         if (method == mTransformation) {
   1163             // Avoid the setText() below if the transformation is
   1164             // the same.
   1165             return;
   1166         }
   1167         if (mTransformation != null) {
   1168             if (mText instanceof Spannable) {
   1169                 ((Spannable) mText).removeSpan(mTransformation);
   1170             }
   1171         }
   1172 
   1173         mTransformation = method;
   1174 
   1175         setText(mText);
   1176     }
   1177 
   1178     /**
   1179      * Returns the top padding of the view, plus space for the top
   1180      * Drawable if any.
   1181      */
   1182     public int getCompoundPaddingTop() {
   1183         final Drawables dr = mDrawables;
   1184         if (dr == null || dr.mDrawableTop == null) {
   1185             return mPaddingTop;
   1186         } else {
   1187             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
   1188         }
   1189     }
   1190 
   1191     /**
   1192      * Returns the bottom padding of the view, plus space for the bottom
   1193      * Drawable if any.
   1194      */
   1195     public int getCompoundPaddingBottom() {
   1196         final Drawables dr = mDrawables;
   1197         if (dr == null || dr.mDrawableBottom == null) {
   1198             return mPaddingBottom;
   1199         } else {
   1200             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
   1201         }
   1202     }
   1203 
   1204     /**
   1205      * Returns the left padding of the view, plus space for the left
   1206      * Drawable if any.
   1207      */
   1208     public int getCompoundPaddingLeft() {
   1209         final Drawables dr = mDrawables;
   1210         if (dr == null || dr.mDrawableLeft == null) {
   1211             return mPaddingLeft;
   1212         } else {
   1213             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
   1214         }
   1215     }
   1216 
   1217     /**
   1218      * Returns the right padding of the view, plus space for the right
   1219      * Drawable if any.
   1220      */
   1221     public int getCompoundPaddingRight() {
   1222         final Drawables dr = mDrawables;
   1223         if (dr == null || dr.mDrawableRight == null) {
   1224             return mPaddingRight;
   1225         } else {
   1226             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
   1227         }
   1228     }
   1229 
   1230     /**
   1231      * Returns the extended top padding of the view, including both the
   1232      * top Drawable if any and any extra space to keep more than maxLines
   1233      * of text from showing.  It is only valid to call this after measuring.
   1234      */
   1235     public int getExtendedPaddingTop() {
   1236         if (mMaxMode != LINES) {
   1237             return getCompoundPaddingTop();
   1238         }
   1239 
   1240         if (mLayout.getLineCount() <= mMaximum) {
   1241             return getCompoundPaddingTop();
   1242         }
   1243 
   1244         int top = getCompoundPaddingTop();
   1245         int bottom = getCompoundPaddingBottom();
   1246         int viewht = getHeight() - top - bottom;
   1247         int layoutht = mLayout.getLineTop(mMaximum);
   1248 
   1249         if (layoutht >= viewht) {
   1250             return top;
   1251         }
   1252 
   1253         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   1254         if (gravity == Gravity.TOP) {
   1255             return top;
   1256         } else if (gravity == Gravity.BOTTOM) {
   1257             return top + viewht - layoutht;
   1258         } else { // (gravity == Gravity.CENTER_VERTICAL)
   1259             return top + (viewht - layoutht) / 2;
   1260         }
   1261     }
   1262 
   1263     /**
   1264      * Returns the extended bottom padding of the view, including both the
   1265      * bottom Drawable if any and any extra space to keep more than maxLines
   1266      * of text from showing.  It is only valid to call this after measuring.
   1267      */
   1268     public int getExtendedPaddingBottom() {
   1269         if (mMaxMode != LINES) {
   1270             return getCompoundPaddingBottom();
   1271         }
   1272 
   1273         if (mLayout.getLineCount() <= mMaximum) {
   1274             return getCompoundPaddingBottom();
   1275         }
   1276 
   1277         int top = getCompoundPaddingTop();
   1278         int bottom = getCompoundPaddingBottom();
   1279         int viewht = getHeight() - top - bottom;
   1280         int layoutht = mLayout.getLineTop(mMaximum);
   1281 
   1282         if (layoutht >= viewht) {
   1283             return bottom;
   1284         }
   1285 
   1286         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   1287         if (gravity == Gravity.TOP) {
   1288             return bottom + viewht - layoutht;
   1289         } else if (gravity == Gravity.BOTTOM) {
   1290             return bottom;
   1291         } else { // (gravity == Gravity.CENTER_VERTICAL)
   1292             return bottom + (viewht - layoutht) / 2;
   1293         }
   1294     }
   1295 
   1296     /**
   1297      * Returns the total left padding of the view, including the left
   1298      * Drawable if any.
   1299      */
   1300     public int getTotalPaddingLeft() {
   1301         return getCompoundPaddingLeft();
   1302     }
   1303 
   1304     /**
   1305      * Returns the total right padding of the view, including the right
   1306      * Drawable if any.
   1307      */
   1308     public int getTotalPaddingRight() {
   1309         return getCompoundPaddingRight();
   1310     }
   1311 
   1312     /**
   1313      * Returns the total top padding of the view, including the top
   1314      * Drawable if any, the extra space to keep more than maxLines
   1315      * from showing, and the vertical offset for gravity, if any.
   1316      */
   1317     public int getTotalPaddingTop() {
   1318         return getExtendedPaddingTop() + getVerticalOffset(true);
   1319     }
   1320 
   1321     /**
   1322      * Returns the total bottom padding of the view, including the bottom
   1323      * Drawable if any, the extra space to keep more than maxLines
   1324      * from showing, and the vertical offset for gravity, if any.
   1325      */
   1326     public int getTotalPaddingBottom() {
   1327         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
   1328     }
   1329 
   1330     /**
   1331      * Sets the Drawables (if any) to appear to the left of, above,
   1332      * to the right of, and below the text.  Use null if you do not
   1333      * want a Drawable there.  The Drawables must already have had
   1334      * {@link Drawable#setBounds} called.
   1335      *
   1336      * @attr ref android.R.styleable#TextView_drawableLeft
   1337      * @attr ref android.R.styleable#TextView_drawableTop
   1338      * @attr ref android.R.styleable#TextView_drawableRight
   1339      * @attr ref android.R.styleable#TextView_drawableBottom
   1340      */
   1341     public void setCompoundDrawables(Drawable left, Drawable top,
   1342                                      Drawable right, Drawable bottom) {
   1343         Drawables dr = mDrawables;
   1344 
   1345         final boolean drawables = left != null || top != null
   1346                 || right != null || bottom != null;
   1347 
   1348         if (!drawables) {
   1349             // Clearing drawables...  can we free the data structure?
   1350             if (dr != null) {
   1351                 if (dr.mDrawablePadding == 0) {
   1352                     mDrawables = null;
   1353                 } else {
   1354                     // We need to retain the last set padding, so just clear
   1355                     // out all of the fields in the existing structure.
   1356                     if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
   1357                     dr.mDrawableLeft = null;
   1358                     if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
   1359                     dr.mDrawableTop = null;
   1360                     if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
   1361                     dr.mDrawableRight = null;
   1362                     if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
   1363                     dr.mDrawableBottom = null;
   1364                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   1365                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   1366                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   1367                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   1368                 }
   1369             }
   1370         } else {
   1371             if (dr == null) {
   1372                 mDrawables = dr = new Drawables();
   1373             }
   1374 
   1375             if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
   1376                 dr.mDrawableLeft.setCallback(null);
   1377             }
   1378             dr.mDrawableLeft = left;
   1379 
   1380             if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
   1381                 dr.mDrawableTop.setCallback(null);
   1382             }
   1383             dr.mDrawableTop = top;
   1384 
   1385             if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
   1386                 dr.mDrawableRight.setCallback(null);
   1387             }
   1388             dr.mDrawableRight = right;
   1389 
   1390             if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
   1391                 dr.mDrawableBottom.setCallback(null);
   1392             }
   1393             dr.mDrawableBottom = bottom;
   1394 
   1395             final Rect compoundRect = dr.mCompoundRect;
   1396             int[] state;
   1397 
   1398             state = getDrawableState();
   1399 
   1400             if (left != null) {
   1401                 left.setState(state);
   1402                 left.copyBounds(compoundRect);
   1403                 left.setCallback(this);
   1404                 dr.mDrawableSizeLeft = compoundRect.width();
   1405                 dr.mDrawableHeightLeft = compoundRect.height();
   1406             } else {
   1407                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   1408             }
   1409 
   1410             if (right != null) {
   1411                 right.setState(state);
   1412                 right.copyBounds(compoundRect);
   1413                 right.setCallback(this);
   1414                 dr.mDrawableSizeRight = compoundRect.width();
   1415                 dr.mDrawableHeightRight = compoundRect.height();
   1416             } else {
   1417                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   1418             }
   1419 
   1420             if (top != null) {
   1421                 top.setState(state);
   1422                 top.copyBounds(compoundRect);
   1423                 top.setCallback(this);
   1424                 dr.mDrawableSizeTop = compoundRect.height();
   1425                 dr.mDrawableWidthTop = compoundRect.width();
   1426             } else {
   1427                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   1428             }
   1429 
   1430             if (bottom != null) {
   1431                 bottom.setState(state);
   1432                 bottom.copyBounds(compoundRect);
   1433                 bottom.setCallback(this);
   1434                 dr.mDrawableSizeBottom = compoundRect.height();
   1435                 dr.mDrawableWidthBottom = compoundRect.width();
   1436             } else {
   1437                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   1438             }
   1439         }
   1440 
   1441         invalidate();
   1442         requestLayout();
   1443     }
   1444 
   1445     /**
   1446      * Sets the Drawables (if any) to appear to the left of, above,
   1447      * to the right of, and below the text.  Use 0 if you do not
   1448      * want a Drawable there. The Drawables' bounds will be set to
   1449      * their intrinsic bounds.
   1450      *
   1451      * @param left Resource identifier of the left Drawable.
   1452      * @param top Resource identifier of the top Drawable.
   1453      * @param right Resource identifier of the right Drawable.
   1454      * @param bottom Resource identifier of the bottom Drawable.
   1455      *
   1456      * @attr ref android.R.styleable#TextView_drawableLeft
   1457      * @attr ref android.R.styleable#TextView_drawableTop
   1458      * @attr ref android.R.styleable#TextView_drawableRight
   1459      * @attr ref android.R.styleable#TextView_drawableBottom
   1460      */
   1461     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
   1462         final Resources resources = getContext().getResources();
   1463         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
   1464                 top != 0 ? resources.getDrawable(top) : null,
   1465                 right != 0 ? resources.getDrawable(right) : null,
   1466                 bottom != 0 ? resources.getDrawable(bottom) : null);
   1467     }
   1468 
   1469     /**
   1470      * Sets the Drawables (if any) to appear to the left of, above,
   1471      * to the right of, and below the text.  Use null if you do not
   1472      * want a Drawable there. The Drawables' bounds will be set to
   1473      * their intrinsic bounds.
   1474      *
   1475      * @attr ref android.R.styleable#TextView_drawableLeft
   1476      * @attr ref android.R.styleable#TextView_drawableTop
   1477      * @attr ref android.R.styleable#TextView_drawableRight
   1478      * @attr ref android.R.styleable#TextView_drawableBottom
   1479      */
   1480     public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
   1481             Drawable right, Drawable bottom) {
   1482 
   1483         if (left != null) {
   1484             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
   1485         }
   1486         if (right != null) {
   1487             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
   1488         }
   1489         if (top != null) {
   1490             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
   1491         }
   1492         if (bottom != null) {
   1493             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
   1494         }
   1495         setCompoundDrawables(left, top, right, bottom);
   1496     }
   1497 
   1498     /**
   1499      * Returns drawables for the left, top, right, and bottom borders.
   1500      */
   1501     public Drawable[] getCompoundDrawables() {
   1502         final Drawables dr = mDrawables;
   1503         if (dr != null) {
   1504             return new Drawable[] {
   1505                 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
   1506             };
   1507         } else {
   1508             return new Drawable[] { null, null, null, null };
   1509         }
   1510     }
   1511 
   1512     /**
   1513      * Sets the size of the padding between the compound drawables and
   1514      * the text.
   1515      *
   1516      * @attr ref android.R.styleable#TextView_drawablePadding
   1517      */
   1518     public void setCompoundDrawablePadding(int pad) {
   1519         Drawables dr = mDrawables;
   1520         if (pad == 0) {
   1521             if (dr != null) {
   1522                 dr.mDrawablePadding = pad;
   1523             }
   1524         } else {
   1525             if (dr == null) {
   1526                 mDrawables = dr = new Drawables();
   1527             }
   1528             dr.mDrawablePadding = pad;
   1529         }
   1530 
   1531         invalidate();
   1532         requestLayout();
   1533     }
   1534 
   1535     /**
   1536      * Returns the padding between the compound drawables and the text.
   1537      */
   1538     public int getCompoundDrawablePadding() {
   1539         final Drawables dr = mDrawables;
   1540         return dr != null ? dr.mDrawablePadding : 0;
   1541     }
   1542 
   1543     @Override
   1544     public void setPadding(int left, int top, int right, int bottom) {
   1545         if (left != mPaddingLeft ||
   1546             right != mPaddingRight ||
   1547             top != mPaddingTop ||
   1548             bottom != mPaddingBottom) {
   1549             nullLayouts();
   1550         }
   1551 
   1552         // the super call will requestLayout()
   1553         super.setPadding(left, top, right, bottom);
   1554         invalidate();
   1555     }
   1556 
   1557     /**
   1558      * Gets the autolink mask of the text.  See {@link
   1559      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   1560      * possible values.
   1561      *
   1562      * @attr ref android.R.styleable#TextView_autoLink
   1563      */
   1564     public final int getAutoLinkMask() {
   1565         return mAutoLinkMask;
   1566     }
   1567 
   1568     /**
   1569      * Sets the text color, size, style, hint color, and highlight color
   1570      * from the specified TextAppearance resource.
   1571      */
   1572     public void setTextAppearance(Context context, int resid) {
   1573         TypedArray appearance =
   1574             context.obtainStyledAttributes(resid,
   1575                                            com.android.internal.R.styleable.TextAppearance);
   1576 
   1577         int color;
   1578         ColorStateList colors;
   1579         int ts;
   1580 
   1581         color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
   1582         if (color != 0) {
   1583             setHighlightColor(color);
   1584         }
   1585 
   1586         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1587                                               TextAppearance_textColor);
   1588         if (colors != null) {
   1589             setTextColor(colors);
   1590         }
   1591 
   1592         ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
   1593                                               TextAppearance_textSize, 0);
   1594         if (ts != 0) {
   1595             setRawTextSize(ts);
   1596         }
   1597 
   1598         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1599                                               TextAppearance_textColorHint);
   1600         if (colors != null) {
   1601             setHintTextColor(colors);
   1602         }
   1603 
   1604         colors = appearance.getColorStateList(com.android.internal.R.styleable.
   1605                                               TextAppearance_textColorLink);
   1606         if (colors != null) {
   1607             setLinkTextColor(colors);
   1608         }
   1609 
   1610         int typefaceIndex, styleIndex;
   1611 
   1612         typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
   1613                                           TextAppearance_typeface, -1);
   1614         styleIndex = appearance.getInt(com.android.internal.R.styleable.
   1615                                        TextAppearance_textStyle, -1);
   1616 
   1617         setTypefaceByIndex(typefaceIndex, styleIndex);
   1618         appearance.recycle();
   1619     }
   1620 
   1621     /**
   1622      * @return the size (in pixels) of the default text size in this TextView.
   1623      */
   1624     public float getTextSize() {
   1625         return mTextPaint.getTextSize();
   1626     }
   1627 
   1628     /**
   1629      * Set the default text size to the given value, interpreted as "scaled
   1630      * pixel" units.  This size is adjusted based on the current density and
   1631      * user font size preference.
   1632      *
   1633      * @param size The scaled pixel size.
   1634      *
   1635      * @attr ref android.R.styleable#TextView_textSize
   1636      */
   1637     @android.view.RemotableViewMethod
   1638     public void setTextSize(float size) {
   1639         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
   1640     }
   1641 
   1642     /**
   1643      * Set the default text size to a given unit and value.  See {@link
   1644      * TypedValue} for the possible dimension units.
   1645      *
   1646      * @param unit The desired dimension unit.
   1647      * @param size The desired size in the given units.
   1648      *
   1649      * @attr ref android.R.styleable#TextView_textSize
   1650      */
   1651     public void setTextSize(int unit, float size) {
   1652         Context c = getContext();
   1653         Resources r;
   1654 
   1655         if (c == null)
   1656             r = Resources.getSystem();
   1657         else
   1658             r = c.getResources();
   1659 
   1660         setRawTextSize(TypedValue.applyDimension(
   1661             unit, size, r.getDisplayMetrics()));
   1662     }
   1663 
   1664     private void setRawTextSize(float size) {
   1665         if (size != mTextPaint.getTextSize()) {
   1666             mTextPaint.setTextSize(size);
   1667 
   1668             if (mLayout != null) {
   1669                 nullLayouts();
   1670                 requestLayout();
   1671                 invalidate();
   1672             }
   1673         }
   1674     }
   1675 
   1676     /**
   1677      * @return the extent by which text is currently being stretched
   1678      * horizontally.  This will usually be 1.
   1679      */
   1680     public float getTextScaleX() {
   1681         return mTextPaint.getTextScaleX();
   1682     }
   1683 
   1684     /**
   1685      * Sets the extent by which text should be stretched horizontally.
   1686      *
   1687      * @attr ref android.R.styleable#TextView_textScaleX
   1688      */
   1689     @android.view.RemotableViewMethod
   1690     public void setTextScaleX(float size) {
   1691         if (size != mTextPaint.getTextScaleX()) {
   1692             mUserSetTextScaleX = true;
   1693             mTextPaint.setTextScaleX(size);
   1694 
   1695             if (mLayout != null) {
   1696                 nullLayouts();
   1697                 requestLayout();
   1698                 invalidate();
   1699             }
   1700         }
   1701     }
   1702 
   1703     /**
   1704      * Sets the typeface and style in which the text should be displayed.
   1705      * Note that not all Typeface families actually have bold and italic
   1706      * variants, so you may need to use
   1707      * {@link #setTypeface(Typeface, int)} to get the appearance
   1708      * that you actually want.
   1709      *
   1710      * @attr ref android.R.styleable#TextView_typeface
   1711      * @attr ref android.R.styleable#TextView_textStyle
   1712      */
   1713     public void setTypeface(Typeface tf) {
   1714         if (mTextPaint.getTypeface() != tf) {
   1715             mTextPaint.setTypeface(tf);
   1716 
   1717             if (mLayout != null) {
   1718                 nullLayouts();
   1719                 requestLayout();
   1720                 invalidate();
   1721             }
   1722         }
   1723     }
   1724 
   1725     /**
   1726      * @return the current typeface and style in which the text is being
   1727      * displayed.
   1728      */
   1729     public Typeface getTypeface() {
   1730         return mTextPaint.getTypeface();
   1731     }
   1732 
   1733     /**
   1734      * Sets the text color for all the states (normal, selected,
   1735      * focused) to be this color.
   1736      *
   1737      * @attr ref android.R.styleable#TextView_textColor
   1738      */
   1739     @android.view.RemotableViewMethod
   1740     public void setTextColor(int color) {
   1741         mTextColor = ColorStateList.valueOf(color);
   1742         updateTextColors();
   1743     }
   1744 
   1745     /**
   1746      * Sets the text color.
   1747      *
   1748      * @attr ref android.R.styleable#TextView_textColor
   1749      */
   1750     public void setTextColor(ColorStateList colors) {
   1751         if (colors == null) {
   1752             throw new NullPointerException();
   1753         }
   1754 
   1755         mTextColor = colors;
   1756         updateTextColors();
   1757     }
   1758 
   1759     /**
   1760      * Return the set of text colors.
   1761      *
   1762      * @return Returns the set of text colors.
   1763      */
   1764     public final ColorStateList getTextColors() {
   1765         return mTextColor;
   1766     }
   1767 
   1768     /**
   1769      * <p>Return the current color selected for normal text.</p>
   1770      *
   1771      * @return Returns the current text color.
   1772      */
   1773     public final int getCurrentTextColor() {
   1774         return mCurTextColor;
   1775     }
   1776 
   1777     /**
   1778      * Sets the color used to display the selection highlight.
   1779      *
   1780      * @attr ref android.R.styleable#TextView_textColorHighlight
   1781      */
   1782     @android.view.RemotableViewMethod
   1783     public void setHighlightColor(int color) {
   1784         if (mHighlightColor != color) {
   1785             mHighlightColor = color;
   1786             invalidate();
   1787         }
   1788     }
   1789 
   1790     /**
   1791      * Gives the text a shadow of the specified radius and color, the specified
   1792      * distance from its normal position.
   1793      *
   1794      * @attr ref android.R.styleable#TextView_shadowColor
   1795      * @attr ref android.R.styleable#TextView_shadowDx
   1796      * @attr ref android.R.styleable#TextView_shadowDy
   1797      * @attr ref android.R.styleable#TextView_shadowRadius
   1798      */
   1799     public void setShadowLayer(float radius, float dx, float dy, int color) {
   1800         mTextPaint.setShadowLayer(radius, dx, dy, color);
   1801 
   1802         mShadowRadius = radius;
   1803         mShadowDx = dx;
   1804         mShadowDy = dy;
   1805 
   1806         invalidate();
   1807     }
   1808 
   1809     /**
   1810      * @return the base paint used for the text.  Please use this only to
   1811      * consult the Paint's properties and not to change them.
   1812      */
   1813     public TextPaint getPaint() {
   1814         return mTextPaint;
   1815     }
   1816 
   1817     /**
   1818      * Sets the autolink mask of the text.  See {@link
   1819      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   1820      * possible values.
   1821      *
   1822      * @attr ref android.R.styleable#TextView_autoLink
   1823      */
   1824     @android.view.RemotableViewMethod
   1825     public final void setAutoLinkMask(int mask) {
   1826         mAutoLinkMask = mask;
   1827     }
   1828 
   1829     /**
   1830      * Sets whether the movement method will automatically be set to
   1831      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   1832      * set to nonzero and links are detected in {@link #setText}.
   1833      * The default is true.
   1834      *
   1835      * @attr ref android.R.styleable#TextView_linksClickable
   1836      */
   1837     @android.view.RemotableViewMethod
   1838     public final void setLinksClickable(boolean whether) {
   1839         mLinksClickable = whether;
   1840     }
   1841 
   1842     /**
   1843      * Returns whether the movement method will automatically be set to
   1844      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   1845      * set to nonzero and links are detected in {@link #setText}.
   1846      * The default is true.
   1847      *
   1848      * @attr ref android.R.styleable#TextView_linksClickable
   1849      */
   1850     public final boolean getLinksClickable() {
   1851         return mLinksClickable;
   1852     }
   1853 
   1854     /**
   1855      * Returns the list of URLSpans attached to the text
   1856      * (by {@link Linkify} or otherwise) if any.  You can call
   1857      * {@link URLSpan#getURL} on them to find where they link to
   1858      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
   1859      * to find the region of the text they are attached to.
   1860      */
   1861     public URLSpan[] getUrls() {
   1862         if (mText instanceof Spanned) {
   1863             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
   1864         } else {
   1865             return new URLSpan[0];
   1866         }
   1867     }
   1868 
   1869     /**
   1870      * Sets the color of the hint text.
   1871      *
   1872      * @attr ref android.R.styleable#TextView_textColorHint
   1873      */
   1874     @android.view.RemotableViewMethod
   1875     public final void setHintTextColor(int color) {
   1876         mHintTextColor = ColorStateList.valueOf(color);
   1877         updateTextColors();
   1878     }
   1879 
   1880     /**
   1881      * Sets the color of the hint text.
   1882      *
   1883      * @attr ref android.R.styleable#TextView_textColorHint
   1884      */
   1885     public final void setHintTextColor(ColorStateList colors) {
   1886         mHintTextColor = colors;
   1887         updateTextColors();
   1888     }
   1889 
   1890     /**
   1891      * <p>Return the color used to paint the hint text.</p>
   1892      *
   1893      * @return Returns the list of hint text colors.
   1894      */
   1895     public final ColorStateList getHintTextColors() {
   1896         return mHintTextColor;
   1897     }
   1898 
   1899     /**
   1900      * <p>Return the current color selected to paint the hint text.</p>
   1901      *
   1902      * @return Returns the current hint text color.
   1903      */
   1904     public final int getCurrentHintTextColor() {
   1905         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
   1906     }
   1907 
   1908     /**
   1909      * Sets the color of links in the text.
   1910      *
   1911      * @attr ref android.R.styleable#TextView_textColorLink
   1912      */
   1913     @android.view.RemotableViewMethod
   1914     public final void setLinkTextColor(int color) {
   1915         mLinkTextColor = ColorStateList.valueOf(color);
   1916         updateTextColors();
   1917     }
   1918 
   1919     /**
   1920      * Sets the color of links in the text.
   1921      *
   1922      * @attr ref android.R.styleable#TextView_textColorLink
   1923      */
   1924     public final void setLinkTextColor(ColorStateList colors) {
   1925         mLinkTextColor = colors;
   1926         updateTextColors();
   1927     }
   1928 
   1929     /**
   1930      * <p>Returns the color used to paint links in the text.</p>
   1931      *
   1932      * @return Returns the list of link text colors.
   1933      */
   1934     public final ColorStateList getLinkTextColors() {
   1935         return mLinkTextColor;
   1936     }
   1937 
   1938     /**
   1939      * Sets the horizontal alignment of the text and the
   1940      * vertical gravity that will be used when there is extra space
   1941      * in the TextView beyond what is required for the text itself.
   1942      *
   1943      * @see android.view.Gravity
   1944      * @attr ref android.R.styleable#TextView_gravity
   1945      */
   1946     public void setGravity(int gravity) {
   1947         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
   1948             gravity |= Gravity.LEFT;
   1949         }
   1950         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
   1951             gravity |= Gravity.TOP;
   1952         }
   1953 
   1954         boolean newLayout = false;
   1955 
   1956         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
   1957             (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
   1958             newLayout = true;
   1959         }
   1960 
   1961         if (gravity != mGravity) {
   1962             invalidate();
   1963         }
   1964 
   1965         mGravity = gravity;
   1966 
   1967         if (mLayout != null && newLayout) {
   1968             // XXX this is heavy-handed because no actual content changes.
   1969             int want = mLayout.getWidth();
   1970             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   1971 
   1972             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   1973                           mRight - mLeft - getCompoundPaddingLeft() -
   1974                           getCompoundPaddingRight(), true);
   1975         }
   1976     }
   1977 
   1978     /**
   1979      * Returns the horizontal and vertical alignment of this TextView.
   1980      *
   1981      * @see android.view.Gravity
   1982      * @attr ref android.R.styleable#TextView_gravity
   1983      */
   1984     public int getGravity() {
   1985         return mGravity;
   1986     }
   1987 
   1988     /**
   1989      * @return the flags on the Paint being used to display the text.
   1990      * @see Paint#getFlags
   1991      */
   1992     public int getPaintFlags() {
   1993         return mTextPaint.getFlags();
   1994     }
   1995 
   1996     /**
   1997      * Sets flags on the Paint being used to display the text and
   1998      * reflows the text if they are different from the old flags.
   1999      * @see Paint#setFlags
   2000      */
   2001     @android.view.RemotableViewMethod
   2002     public void setPaintFlags(int flags) {
   2003         if (mTextPaint.getFlags() != flags) {
   2004             mTextPaint.setFlags(flags);
   2005 
   2006             if (mLayout != null) {
   2007                 nullLayouts();
   2008                 requestLayout();
   2009                 invalidate();
   2010             }
   2011         }
   2012     }
   2013 
   2014     /**
   2015      * Sets whether the text should be allowed to be wider than the
   2016      * View is.  If false, it will be wrapped to the width of the View.
   2017      *
   2018      * @attr ref android.R.styleable#TextView_scrollHorizontally
   2019      */
   2020     public void setHorizontallyScrolling(boolean whether) {
   2021         mHorizontallyScrolling = whether;
   2022 
   2023         if (mLayout != null) {
   2024             nullLayouts();
   2025             requestLayout();
   2026             invalidate();
   2027         }
   2028     }
   2029 
   2030     /**
   2031      * Makes the TextView at least this many lines tall
   2032      *
   2033      * @attr ref android.R.styleable#TextView_minLines
   2034      */
   2035     @android.view.RemotableViewMethod
   2036     public void setMinLines(int minlines) {
   2037         mMinimum = minlines;
   2038         mMinMode = LINES;
   2039 
   2040         requestLayout();
   2041         invalidate();
   2042     }
   2043 
   2044     /**
   2045      * Makes the TextView at least this many pixels tall
   2046      *
   2047      * @attr ref android.R.styleable#TextView_minHeight
   2048      */
   2049     @android.view.RemotableViewMethod
   2050     public void setMinHeight(int minHeight) {
   2051         mMinimum = minHeight;
   2052         mMinMode = PIXELS;
   2053 
   2054         requestLayout();
   2055         invalidate();
   2056     }
   2057 
   2058     /**
   2059      * Makes the TextView at most this many lines tall
   2060      *
   2061      * @attr ref android.R.styleable#TextView_maxLines
   2062      */
   2063     @android.view.RemotableViewMethod
   2064     public void setMaxLines(int maxlines) {
   2065         mMaximum = maxlines;
   2066         mMaxMode = LINES;
   2067 
   2068         requestLayout();
   2069         invalidate();
   2070     }
   2071 
   2072     /**
   2073      * Makes the TextView at most this many pixels tall
   2074      *
   2075      * @attr ref android.R.styleable#TextView_maxHeight
   2076      */
   2077     @android.view.RemotableViewMethod
   2078     public void setMaxHeight(int maxHeight) {
   2079         mMaximum = maxHeight;
   2080         mMaxMode = PIXELS;
   2081 
   2082         requestLayout();
   2083         invalidate();
   2084     }
   2085 
   2086     /**
   2087      * Makes the TextView exactly this many lines tall
   2088      *
   2089      * @attr ref android.R.styleable#TextView_lines
   2090      */
   2091     @android.view.RemotableViewMethod
   2092     public void setLines(int lines) {
   2093         mMaximum = mMinimum = lines;
   2094         mMaxMode = mMinMode = LINES;
   2095 
   2096         requestLayout();
   2097         invalidate();
   2098     }
   2099 
   2100     /**
   2101      * Makes the TextView exactly this many pixels tall.
   2102      * You could do the same thing by specifying this number in the
   2103      * LayoutParams.
   2104      *
   2105      * @attr ref android.R.styleable#TextView_height
   2106      */
   2107     @android.view.RemotableViewMethod
   2108     public void setHeight(int pixels) {
   2109         mMaximum = mMinimum = pixels;
   2110         mMaxMode = mMinMode = PIXELS;
   2111 
   2112         requestLayout();
   2113         invalidate();
   2114     }
   2115 
   2116     /**
   2117      * Makes the TextView at least this many ems wide
   2118      *
   2119      * @attr ref android.R.styleable#TextView_minEms
   2120      */
   2121     @android.view.RemotableViewMethod
   2122     public void setMinEms(int minems) {
   2123         mMinWidth = minems;
   2124         mMinWidthMode = EMS;
   2125 
   2126         requestLayout();
   2127         invalidate();
   2128     }
   2129 
   2130     /**
   2131      * Makes the TextView at least this many pixels wide
   2132      *
   2133      * @attr ref android.R.styleable#TextView_minWidth
   2134      */
   2135     @android.view.RemotableViewMethod
   2136     public void setMinWidth(int minpixels) {
   2137         mMinWidth = minpixels;
   2138         mMinWidthMode = PIXELS;
   2139 
   2140         requestLayout();
   2141         invalidate();
   2142     }
   2143 
   2144     /**
   2145      * Makes the TextView at most this many ems wide
   2146      *
   2147      * @attr ref android.R.styleable#TextView_maxEms
   2148      */
   2149     @android.view.RemotableViewMethod
   2150     public void setMaxEms(int maxems) {
   2151         mMaxWidth = maxems;
   2152         mMaxWidthMode = EMS;
   2153 
   2154         requestLayout();
   2155         invalidate();
   2156     }
   2157 
   2158     /**
   2159      * Makes the TextView at most this many pixels wide
   2160      *
   2161      * @attr ref android.R.styleable#TextView_maxWidth
   2162      */
   2163     @android.view.RemotableViewMethod
   2164     public void setMaxWidth(int maxpixels) {
   2165         mMaxWidth = maxpixels;
   2166         mMaxWidthMode = PIXELS;
   2167 
   2168         requestLayout();
   2169         invalidate();
   2170     }
   2171 
   2172     /**
   2173      * Makes the TextView exactly this many ems wide
   2174      *
   2175      * @attr ref android.R.styleable#TextView_ems
   2176      */
   2177     @android.view.RemotableViewMethod
   2178     public void setEms(int ems) {
   2179         mMaxWidth = mMinWidth = ems;
   2180         mMaxWidthMode = mMinWidthMode = EMS;
   2181 
   2182         requestLayout();
   2183         invalidate();
   2184     }
   2185 
   2186     /**
   2187      * Makes the TextView exactly this many pixels wide.
   2188      * You could do the same thing by specifying this number in the
   2189      * LayoutParams.
   2190      *
   2191      * @attr ref android.R.styleable#TextView_width
   2192      */
   2193     @android.view.RemotableViewMethod
   2194     public void setWidth(int pixels) {
   2195         mMaxWidth = mMinWidth = pixels;
   2196         mMaxWidthMode = mMinWidthMode = PIXELS;
   2197 
   2198         requestLayout();
   2199         invalidate();
   2200     }
   2201 
   2202 
   2203     /**
   2204      * Sets line spacing for this TextView.  Each line will have its height
   2205      * multiplied by <code>mult</code> and have <code>add</code> added to it.
   2206      *
   2207      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   2208      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   2209      */
   2210     public void setLineSpacing(float add, float mult) {
   2211         mSpacingMult = mult;
   2212         mSpacingAdd = add;
   2213 
   2214         if (mLayout != null) {
   2215             nullLayouts();
   2216             requestLayout();
   2217             invalidate();
   2218         }
   2219     }
   2220 
   2221     /**
   2222      * Convenience method: Append the specified text to the TextView's
   2223      * display buffer, upgrading it to BufferType.EDITABLE if it was
   2224      * not already editable.
   2225      */
   2226     public final void append(CharSequence text) {
   2227         append(text, 0, text.length());
   2228     }
   2229 
   2230     /**
   2231      * Convenience method: Append the specified text slice to the TextView's
   2232      * display buffer, upgrading it to BufferType.EDITABLE if it was
   2233      * not already editable.
   2234      */
   2235     public void append(CharSequence text, int start, int end) {
   2236         if (!(mText instanceof Editable)) {
   2237             setText(mText, BufferType.EDITABLE);
   2238         }
   2239 
   2240         ((Editable) mText).append(text, start, end);
   2241     }
   2242 
   2243     private void updateTextColors() {
   2244         boolean inval = false;
   2245         int color = mTextColor.getColorForState(getDrawableState(), 0);
   2246         if (color != mCurTextColor) {
   2247             mCurTextColor = color;
   2248             inval = true;
   2249         }
   2250         if (mLinkTextColor != null) {
   2251             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
   2252             if (color != mTextPaint.linkColor) {
   2253                 mTextPaint.linkColor = color;
   2254                 inval = true;
   2255             }
   2256         }
   2257         if (mHintTextColor != null) {
   2258             color = mHintTextColor.getColorForState(getDrawableState(), 0);
   2259             if (color != mCurHintTextColor && mText.length() == 0) {
   2260                 mCurHintTextColor = color;
   2261                 inval = true;
   2262             }
   2263         }
   2264         if (inval) {
   2265             invalidate();
   2266         }
   2267     }
   2268 
   2269     @Override
   2270     protected void drawableStateChanged() {
   2271         super.drawableStateChanged();
   2272         if (mTextColor != null && mTextColor.isStateful()
   2273                 || (mHintTextColor != null && mHintTextColor.isStateful())
   2274                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
   2275             updateTextColors();
   2276         }
   2277 
   2278         final Drawables dr = mDrawables;
   2279         if (dr != null) {
   2280             int[] state = getDrawableState();
   2281             if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
   2282                 dr.mDrawableTop.setState(state);
   2283             }
   2284             if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
   2285                 dr.mDrawableBottom.setState(state);
   2286             }
   2287             if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
   2288                 dr.mDrawableLeft.setState(state);
   2289             }
   2290             if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
   2291                 dr.mDrawableRight.setState(state);
   2292             }
   2293         }
   2294     }
   2295 
   2296     /**
   2297      * User interface state that is stored by TextView for implementing
   2298      * {@link View#onSaveInstanceState}.
   2299      */
   2300     public static class SavedState extends BaseSavedState {
   2301         int selStart;
   2302         int selEnd;
   2303         CharSequence text;
   2304         boolean frozenWithFocus;
   2305         CharSequence error;
   2306 
   2307         SavedState(Parcelable superState) {
   2308             super(superState);
   2309         }
   2310 
   2311         @Override
   2312         public void writeToParcel(Parcel out, int flags) {
   2313             super.writeToParcel(out, flags);
   2314             out.writeInt(selStart);
   2315             out.writeInt(selEnd);
   2316             out.writeInt(frozenWithFocus ? 1 : 0);
   2317             TextUtils.writeToParcel(text, out, flags);
   2318 
   2319             if (error == null) {
   2320                 out.writeInt(0);
   2321             } else {
   2322                 out.writeInt(1);
   2323                 TextUtils.writeToParcel(error, out, flags);
   2324             }
   2325         }
   2326 
   2327         @Override
   2328         public String toString() {
   2329             String str = "TextView.SavedState{"
   2330                     + Integer.toHexString(System.identityHashCode(this))
   2331                     + " start=" + selStart + " end=" + selEnd;
   2332             if (text != null) {
   2333                 str += " text=" + text;
   2334             }
   2335             return str + "}";
   2336         }
   2337 
   2338         public static final Parcelable.Creator<SavedState> CREATOR
   2339                 = new Parcelable.Creator<SavedState>() {
   2340             public SavedState createFromParcel(Parcel in) {
   2341                 return new SavedState(in);
   2342             }
   2343 
   2344             public SavedState[] newArray(int size) {
   2345                 return new SavedState[size];
   2346             }
   2347         };
   2348 
   2349         private SavedState(Parcel in) {
   2350             super(in);
   2351             selStart = in.readInt();
   2352             selEnd = in.readInt();
   2353             frozenWithFocus = (in.readInt() != 0);
   2354             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   2355 
   2356             if (in.readInt() != 0) {
   2357                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   2358             }
   2359         }
   2360     }
   2361 
   2362     @Override
   2363     public Parcelable onSaveInstanceState() {
   2364         Parcelable superState = super.onSaveInstanceState();
   2365 
   2366         // Save state if we are forced to
   2367         boolean save = mFreezesText;
   2368         int start = 0;
   2369         int end = 0;
   2370 
   2371         if (mText != null) {
   2372             start = Selection.getSelectionStart(mText);
   2373             end = Selection.getSelectionEnd(mText);
   2374             if (start >= 0 || end >= 0) {
   2375                 // Or save state if there is a selection
   2376                 save = true;
   2377             }
   2378         }
   2379 
   2380         if (save) {
   2381             SavedState ss = new SavedState(superState);
   2382             // XXX Should also save the current scroll position!
   2383             ss.selStart = start;
   2384             ss.selEnd = end;
   2385 
   2386             if (mText instanceof Spanned) {
   2387                 /*
   2388                  * Calling setText() strips off any ChangeWatchers;
   2389                  * strip them now to avoid leaking references.
   2390                  * But do it to a copy so that if there are any
   2391                  * further changes to the text of this view, it
   2392                  * won't get into an inconsistent state.
   2393                  */
   2394 
   2395                 Spannable sp = new SpannableString(mText);
   2396 
   2397                 for (ChangeWatcher cw :
   2398                      sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
   2399                     sp.removeSpan(cw);
   2400                 }
   2401 
   2402                 ss.text = sp;
   2403             } else {
   2404                 ss.text = mText.toString();
   2405             }
   2406 
   2407             if (isFocused() && start >= 0 && end >= 0) {
   2408                 ss.frozenWithFocus = true;
   2409             }
   2410 
   2411             ss.error = mError;
   2412 
   2413             return ss;
   2414         }
   2415 
   2416         return superState;
   2417     }
   2418 
   2419     @Override
   2420     public void onRestoreInstanceState(Parcelable state) {
   2421         if (!(state instanceof SavedState)) {
   2422             super.onRestoreInstanceState(state);
   2423             return;
   2424         }
   2425 
   2426         SavedState ss = (SavedState)state;
   2427         super.onRestoreInstanceState(ss.getSuperState());
   2428 
   2429         // XXX restore buffer type too, as well as lots of other stuff
   2430         if (ss.text != null) {
   2431             setText(ss.text);
   2432         }
   2433 
   2434         if (ss.selStart >= 0 && ss.selEnd >= 0) {
   2435             if (mText instanceof Spannable) {
   2436                 int len = mText.length();
   2437 
   2438                 if (ss.selStart > len || ss.selEnd > len) {
   2439                     String restored = "";
   2440 
   2441                     if (ss.text != null) {
   2442                         restored = "(restored) ";
   2443                     }
   2444 
   2445                     Log.e("TextView", "Saved cursor position " + ss.selStart +
   2446                           "/" + ss.selEnd + " out of range for " + restored +
   2447                           "text " + mText);
   2448                 } else {
   2449                     Selection.setSelection((Spannable) mText, ss.selStart,
   2450                                            ss.selEnd);
   2451 
   2452                     if (ss.frozenWithFocus) {
   2453                         mFrozenWithFocus = true;
   2454                     }
   2455                 }
   2456             }
   2457         }
   2458 
   2459         if (ss.error != null) {
   2460             final CharSequence error = ss.error;
   2461             // Display the error later, after the first layout pass
   2462             post(new Runnable() {
   2463                 public void run() {
   2464                     setError(error);
   2465                 }
   2466             });
   2467         }
   2468     }
   2469 
   2470     /**
   2471      * Control whether this text view saves its entire text contents when
   2472      * freezing to an icicle, in addition to dynamic state such as cursor
   2473      * position.  By default this is false, not saving the text.  Set to true
   2474      * if the text in the text view is not being saved somewhere else in
   2475      * persistent storage (such as in a content provider) so that if the
   2476      * view is later thawed the user will not lose their data.
   2477      *
   2478      * @param freezesText Controls whether a frozen icicle should include the
   2479      * entire text data: true to include it, false to not.
   2480      *
   2481      * @attr ref android.R.styleable#TextView_freezesText
   2482      */
   2483     @android.view.RemotableViewMethod
   2484     public void setFreezesText(boolean freezesText) {
   2485         mFreezesText = freezesText;
   2486     }
   2487 
   2488     /**
   2489      * Return whether this text view is including its entire text contents
   2490      * in frozen icicles.
   2491      *
   2492      * @return Returns true if text is included, false if it isn't.
   2493      *
   2494      * @see #setFreezesText
   2495      */
   2496     public boolean getFreezesText() {
   2497         return mFreezesText;
   2498     }
   2499 
   2500     ///////////////////////////////////////////////////////////////////////////
   2501 
   2502     /**
   2503      * Sets the Factory used to create new Editables.
   2504      */
   2505     public final void setEditableFactory(Editable.Factory factory) {
   2506         mEditableFactory = factory;
   2507         setText(mText);
   2508     }
   2509 
   2510     /**
   2511      * Sets the Factory used to create new Spannables.
   2512      */
   2513     public final void setSpannableFactory(Spannable.Factory factory) {
   2514         mSpannableFactory = factory;
   2515         setText(mText);
   2516     }
   2517 
   2518     /**
   2519      * Sets the string value of the TextView. TextView <em>does not</em> accept
   2520      * HTML-like formatting, which you can do with text strings in XML resource files.
   2521      * To style your strings, attach android.text.style.* objects to a
   2522      * {@link android.text.SpannableString SpannableString}, or see the
   2523      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
   2524      * Available Resource Types</a> documentation for an example of setting
   2525      * formatted text in the XML resource file.
   2526      *
   2527      * @attr ref android.R.styleable#TextView_text
   2528      */
   2529     @android.view.RemotableViewMethod
   2530     public final void setText(CharSequence text) {
   2531         setText(text, mBufferType);
   2532     }
   2533 
   2534     /**
   2535      * Like {@link #setText(CharSequence)},
   2536      * except that the cursor position (if any) is retained in the new text.
   2537      *
   2538      * @param text The new text to place in the text view.
   2539      *
   2540      * @see #setText(CharSequence)
   2541      */
   2542     @android.view.RemotableViewMethod
   2543     public final void setTextKeepState(CharSequence text) {
   2544         setTextKeepState(text, mBufferType);
   2545     }
   2546 
   2547     /**
   2548      * Sets the text that this TextView is to display (see
   2549      * {@link #setText(CharSequence)}) and also sets whether it is stored
   2550      * in a styleable/spannable buffer and whether it is editable.
   2551      *
   2552      * @attr ref android.R.styleable#TextView_text
   2553      * @attr ref android.R.styleable#TextView_bufferType
   2554      */
   2555     public void setText(CharSequence text, BufferType type) {
   2556         setText(text, type, true, 0);
   2557 
   2558         if (mCharWrapper != null) {
   2559             mCharWrapper.mChars = null;
   2560         }
   2561     }
   2562 
   2563     private void setText(CharSequence text, BufferType type,
   2564                          boolean notifyBefore, int oldlen) {
   2565         if (text == null) {
   2566             text = "";
   2567         }
   2568 
   2569         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
   2570 
   2571         if (text instanceof Spanned &&
   2572             ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
   2573             setHorizontalFadingEdgeEnabled(true);
   2574             setEllipsize(TextUtils.TruncateAt.MARQUEE);
   2575         }
   2576 
   2577         int n = mFilters.length;
   2578         for (int i = 0; i < n; i++) {
   2579             CharSequence out = mFilters[i].filter(text, 0, text.length(),
   2580                                                   EMPTY_SPANNED, 0, 0);
   2581             if (out != null) {
   2582                 text = out;
   2583             }
   2584         }
   2585 
   2586         if (notifyBefore) {
   2587             if (mText != null) {
   2588                 oldlen = mText.length();
   2589                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
   2590             } else {
   2591                 sendBeforeTextChanged("", 0, 0, text.length());
   2592             }
   2593         }
   2594 
   2595         boolean needEditableForNotification = false;
   2596 
   2597         if (mListeners != null && mListeners.size() != 0) {
   2598             needEditableForNotification = true;
   2599         }
   2600 
   2601         if (type == BufferType.EDITABLE || mInput != null ||
   2602             needEditableForNotification) {
   2603             Editable t = mEditableFactory.newEditable(text);
   2604             text = t;
   2605             setFilters(t, mFilters);
   2606             InputMethodManager imm = InputMethodManager.peekInstance();
   2607             if (imm != null) imm.restartInput(this);
   2608         } else if (type == BufferType.SPANNABLE || mMovement != null) {
   2609             text = mSpannableFactory.newSpannable(text);
   2610         } else if (!(text instanceof CharWrapper)) {
   2611             text = TextUtils.stringOrSpannedString(text);
   2612         }
   2613 
   2614         if (mAutoLinkMask != 0) {
   2615             Spannable s2;
   2616 
   2617             if (type == BufferType.EDITABLE || text instanceof Spannable) {
   2618                 s2 = (Spannable) text;
   2619             } else {
   2620                 s2 = mSpannableFactory.newSpannable(text);
   2621             }
   2622 
   2623             if (Linkify.addLinks(s2, mAutoLinkMask)) {
   2624                 text = s2;
   2625                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
   2626 
   2627                 /*
   2628                  * We must go ahead and set the text before changing the
   2629                  * movement method, because setMovementMethod() may call
   2630                  * setText() again to try to upgrade the buffer type.
   2631                  */
   2632                 mText = text;
   2633 
   2634                 if (mLinksClickable) {
   2635                     setMovementMethod(LinkMovementMethod.getInstance());
   2636                 }
   2637             }
   2638         }
   2639 
   2640         mBufferType = type;
   2641         mText = text;
   2642 
   2643         if (mTransformation == null)
   2644             mTransformed = text;
   2645         else
   2646             mTransformed = mTransformation.getTransformation(text, this);
   2647 
   2648         final int textLength = text.length();
   2649 
   2650         if (text instanceof Spannable) {
   2651             Spannable sp = (Spannable) text;
   2652 
   2653             // Remove any ChangeWatchers that might have come
   2654             // from other TextViews.
   2655             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
   2656             final int count = watchers.length;
   2657             for (int i = 0; i < count; i++)
   2658                 sp.removeSpan(watchers[i]);
   2659 
   2660             if (mChangeWatcher == null)
   2661                 mChangeWatcher = new ChangeWatcher();
   2662 
   2663             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
   2664                        (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
   2665 
   2666             if (mInput != null) {
   2667                 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   2668             }
   2669 
   2670             if (mTransformation != null) {
   2671                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   2672 
   2673             }
   2674 
   2675             if (mMovement != null) {
   2676                 mMovement.initialize(this, (Spannable) text);
   2677 
   2678                 /*
   2679                  * Initializing the movement method will have set the
   2680                  * selection, so reset mSelectionMoved to keep that from
   2681                  * interfering with the normal on-focus selection-setting.
   2682                  */
   2683                 mSelectionMoved = false;
   2684             }
   2685         }
   2686 
   2687         if (mLayout != null) {
   2688             checkForRelayout();
   2689         }
   2690 
   2691         sendOnTextChanged(text, 0, oldlen, textLength);
   2692         onTextChanged(text, 0, oldlen, textLength);
   2693 
   2694         if (needEditableForNotification) {
   2695             sendAfterTextChanged((Editable) text);
   2696         }
   2697     }
   2698 
   2699     /**
   2700      * Sets the TextView to display the specified slice of the specified
   2701      * char array.  You must promise that you will not change the contents
   2702      * of the array except for right before another call to setText(),
   2703      * since the TextView has no way to know that the text
   2704      * has changed and that it needs to invalidate and re-layout.
   2705      */
   2706     public final void setText(char[] text, int start, int len) {
   2707         int oldlen = 0;
   2708 
   2709         if (start < 0 || len < 0 || start + len > text.length) {
   2710             throw new IndexOutOfBoundsException(start + ", " + len);
   2711         }
   2712 
   2713         /*
   2714          * We must do the before-notification here ourselves because if
   2715          * the old text is a CharWrapper we destroy it before calling
   2716          * into the normal path.
   2717          */
   2718         if (mText != null) {
   2719             oldlen = mText.length();
   2720             sendBeforeTextChanged(mText, 0, oldlen, len);
   2721         } else {
   2722             sendBeforeTextChanged("", 0, 0, len);
   2723         }
   2724 
   2725         if (mCharWrapper == null) {
   2726             mCharWrapper = new CharWrapper(text, start, len);
   2727         } else {
   2728             mCharWrapper.set(text, start, len);
   2729         }
   2730 
   2731         setText(mCharWrapper, mBufferType, false, oldlen);
   2732     }
   2733 
   2734     private static class CharWrapper
   2735             implements CharSequence, GetChars, GraphicsOperations {
   2736         private char[] mChars;
   2737         private int mStart, mLength;
   2738 
   2739         public CharWrapper(char[] chars, int start, int len) {
   2740             mChars = chars;
   2741             mStart = start;
   2742             mLength = len;
   2743         }
   2744 
   2745         /* package */ void set(char[] chars, int start, int len) {
   2746             mChars = chars;
   2747             mStart = start;
   2748             mLength = len;
   2749         }
   2750 
   2751         public int length() {
   2752             return mLength;
   2753         }
   2754 
   2755         public char charAt(int off) {
   2756             return mChars[off + mStart];
   2757         }
   2758 
   2759         public String toString() {
   2760             return new String(mChars, mStart, mLength);
   2761         }
   2762 
   2763         public CharSequence subSequence(int start, int end) {
   2764             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   2765                 throw new IndexOutOfBoundsException(start + ", " + end);
   2766             }
   2767 
   2768             return new String(mChars, start + mStart, end - start);
   2769         }
   2770 
   2771         public void getChars(int start, int end, char[] buf, int off) {
   2772             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   2773                 throw new IndexOutOfBoundsException(start + ", " + end);
   2774             }
   2775 
   2776             System.arraycopy(mChars, start + mStart, buf, off, end - start);
   2777         }
   2778 
   2779         public void drawText(Canvas c, int start, int end,
   2780                              float x, float y, Paint p) {
   2781             c.drawText(mChars, start + mStart, end - start, x, y, p);
   2782         }
   2783 
   2784         public float measureText(int start, int end, Paint p) {
   2785             return p.measureText(mChars, start + mStart, end - start);
   2786         }
   2787 
   2788         public int getTextWidths(int start, int end, float[] widths, Paint p) {
   2789             return p.getTextWidths(mChars, start + mStart, end - start, widths);
   2790         }
   2791     }
   2792 
   2793     /**
   2794      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
   2795      * except that the cursor position (if any) is retained in the new text.
   2796      *
   2797      * @see #setText(CharSequence, android.widget.TextView.BufferType)
   2798      */
   2799     public final void setTextKeepState(CharSequence text, BufferType type) {
   2800         int start = getSelectionStart();
   2801         int end = getSelectionEnd();
   2802         int len = text.length();
   2803 
   2804         setText(text, type);
   2805 
   2806         if (start >= 0 || end >= 0) {
   2807             if (mText instanceof Spannable) {
   2808                 Selection.setSelection((Spannable) mText,
   2809                                        Math.max(0, Math.min(start, len)),
   2810                                        Math.max(0, Math.min(end, len)));
   2811             }
   2812         }
   2813     }
   2814 
   2815     @android.view.RemotableViewMethod
   2816     public final void setText(int resid) {
   2817         setText(getContext().getResources().getText(resid));
   2818     }
   2819 
   2820     public final void setText(int resid, BufferType type) {
   2821         setText(getContext().getResources().getText(resid), type);
   2822     }
   2823 
   2824     /**
   2825      * Sets the text to be displayed when the text of the TextView is empty.
   2826      * Null means to use the normal empty text. The hint does not currently
   2827      * participate in determining the size of the view.
   2828      *
   2829      * @attr ref android.R.styleable#TextView_hint
   2830      */
   2831     @android.view.RemotableViewMethod
   2832     public final void setHint(CharSequence hint) {
   2833         mHint = TextUtils.stringOrSpannedString(hint);
   2834 
   2835         if (mLayout != null) {
   2836             checkForRelayout();
   2837         }
   2838 
   2839         if (mText.length() == 0) {
   2840             invalidate();
   2841         }
   2842     }
   2843 
   2844     /**
   2845      * Sets the text to be displayed when the text of the TextView is empty,
   2846      * from a resource.
   2847      *
   2848      * @attr ref android.R.styleable#TextView_hint
   2849      */
   2850     @android.view.RemotableViewMethod
   2851     public final void setHint(int resid) {
   2852         setHint(getContext().getResources().getText(resid));
   2853     }
   2854 
   2855     /**
   2856      * Returns the hint that is displayed when the text of the TextView
   2857      * is empty.
   2858      *
   2859      * @attr ref android.R.styleable#TextView_hint
   2860      */
   2861     @ViewDebug.CapturedViewProperty
   2862     public CharSequence getHint() {
   2863         return mHint;
   2864     }
   2865 
   2866     /**
   2867      * Set the type of the content with a constant as defined for
   2868      * {@link EditorInfo#inputType}.  This will take care of changing
   2869      * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
   2870      * match the given content type.  If the given content type is
   2871      * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
   2872      * not be displayed for this text view.
   2873      *
   2874      * @see #getInputType()
   2875      * @see #setRawInputType(int)
   2876      * @see android.text.InputType
   2877      * @attr ref android.R.styleable#TextView_inputType
   2878      */
   2879     public void setInputType(int type) {
   2880         final boolean wasPassword = isPasswordInputType(mInputType);
   2881         final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
   2882         setInputType(type, false);
   2883         final boolean isPassword = isPasswordInputType(type);
   2884         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
   2885         boolean forceUpdate = false;
   2886         if (isPassword) {
   2887             setTransformationMethod(PasswordTransformationMethod.getInstance());
   2888             setTypefaceByIndex(MONOSPACE, 0);
   2889         } else if (isVisiblePassword) {
   2890             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   2891                 forceUpdate = true;
   2892             }
   2893             setTypefaceByIndex(MONOSPACE, 0);
   2894         } else if (wasPassword || wasVisiblePassword) {
   2895             // not in password mode, clean up typeface and transformation
   2896             setTypefaceByIndex(-1, -1);
   2897             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   2898                 forceUpdate = true;
   2899             }
   2900         }
   2901 
   2902         boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
   2903                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
   2904                 (EditorInfo.TYPE_CLASS_TEXT
   2905                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
   2906 
   2907         // We need to update the single line mode if it has changed or we
   2908         // were previously in password mode.
   2909         if (mSingleLine == multiLine || forceUpdate) {
   2910             // Change single line mode, but only change the transformation if
   2911             // we are not in password mode.
   2912             applySingleLine(!multiLine, !isPassword);
   2913         }
   2914 
   2915         InputMethodManager imm = InputMethodManager.peekInstance();
   2916         if (imm != null) imm.restartInput(this);
   2917     }
   2918 
   2919     private boolean isPasswordInputType(int inputType) {
   2920         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
   2921                 | EditorInfo.TYPE_MASK_VARIATION);
   2922         return variation
   2923                 == (EditorInfo.TYPE_CLASS_TEXT
   2924                         | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
   2925     }
   2926 
   2927     private boolean isVisiblePasswordInputType(int inputType) {
   2928         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
   2929                 | EditorInfo.TYPE_MASK_VARIATION);
   2930         return variation
   2931                 == (EditorInfo.TYPE_CLASS_TEXT
   2932                         | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
   2933     }
   2934 
   2935     /**
   2936      * Directly change the content type integer of the text view, without
   2937      * modifying any other state.
   2938      * @see #setInputType(int)
   2939      * @see android.text.InputType
   2940      * @attr ref android.R.styleable#TextView_inputType
   2941      */
   2942     public void setRawInputType(int type) {
   2943         mInputType = type;
   2944     }
   2945 
   2946     private void setInputType(int type, boolean direct) {
   2947         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
   2948         KeyListener input;
   2949         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
   2950             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
   2951                     != 0;
   2952             TextKeyListener.Capitalize cap;
   2953             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
   2954                 cap = TextKeyListener.Capitalize.CHARACTERS;
   2955             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
   2956                 cap = TextKeyListener.Capitalize.WORDS;
   2957             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
   2958                 cap = TextKeyListener.Capitalize.SENTENCES;
   2959             } else {
   2960                 cap = TextKeyListener.Capitalize.NONE;
   2961             }
   2962             input = TextKeyListener.getInstance(autotext, cap);
   2963         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
   2964             input = DigitsKeyListener.getInstance(
   2965                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
   2966                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
   2967         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
   2968             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
   2969                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
   2970                     input = DateKeyListener.getInstance();
   2971                     break;
   2972                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
   2973                     input = TimeKeyListener.getInstance();
   2974                     break;
   2975                 default:
   2976                     input = DateTimeKeyListener.getInstance();
   2977                     break;
   2978             }
   2979         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
   2980             input = DialerKeyListener.getInstance();
   2981         } else {
   2982             input = TextKeyListener.getInstance();
   2983         }
   2984         mInputType = type;
   2985         if (direct) mInput = input;
   2986         else {
   2987             setKeyListenerOnly(input);
   2988         }
   2989     }
   2990 
   2991     /**
   2992      * Get the type of the content.
   2993      *
   2994      * @see #setInputType(int)
   2995      * @see android.text.InputType
   2996      */
   2997     public int getInputType() {
   2998         return mInputType;
   2999     }
   3000 
   3001     /**
   3002      * Change the editor type integer associated with the text view, which
   3003      * will be reported to an IME with {@link EditorInfo#imeOptions} when it
   3004      * has focus.
   3005      * @see #getImeOptions
   3006      * @see android.view.inputmethod.EditorInfo
   3007      * @attr ref android.R.styleable#TextView_imeOptions
   3008      */
   3009     public void setImeOptions(int imeOptions) {
   3010         if (mInputContentType == null) {
   3011             mInputContentType = new InputContentType();
   3012         }
   3013         mInputContentType.imeOptions = imeOptions;
   3014     }
   3015 
   3016     /**
   3017      * Get the type of the IME editor.
   3018      *
   3019      * @see #setImeOptions(int)
   3020      * @see android.view.inputmethod.EditorInfo
   3021      */
   3022     public int getImeOptions() {
   3023         return mInputContentType != null
   3024                 ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
   3025     }
   3026 
   3027     /**
   3028      * Change the custom IME action associated with the text view, which
   3029      * will be reported to an IME with {@link EditorInfo#actionLabel}
   3030      * and {@link EditorInfo#actionId} when it has focus.
   3031      * @see #getImeActionLabel
   3032      * @see #getImeActionId
   3033      * @see android.view.inputmethod.EditorInfo
   3034      * @attr ref android.R.styleable#TextView_imeActionLabel
   3035      * @attr ref android.R.styleable#TextView_imeActionId
   3036      */
   3037     public void setImeActionLabel(CharSequence label, int actionId) {
   3038         if (mInputContentType == null) {
   3039             mInputContentType = new InputContentType();
   3040         }
   3041         mInputContentType.imeActionLabel = label;
   3042         mInputContentType.imeActionId = actionId;
   3043     }
   3044 
   3045     /**
   3046      * Get the IME action label previous set with {@link #setImeActionLabel}.
   3047      *
   3048      * @see #setImeActionLabel
   3049      * @see android.view.inputmethod.EditorInfo
   3050      */
   3051     public CharSequence getImeActionLabel() {
   3052         return mInputContentType != null
   3053                 ? mInputContentType.imeActionLabel : null;
   3054     }
   3055 
   3056     /**
   3057      * Get the IME action ID previous set with {@link #setImeActionLabel}.
   3058      *
   3059      * @see #setImeActionLabel
   3060      * @see android.view.inputmethod.EditorInfo
   3061      */
   3062     public int getImeActionId() {
   3063         return mInputContentType != null
   3064                 ? mInputContentType.imeActionId : 0;
   3065     }
   3066 
   3067     /**
   3068      * Set a special listener to be called when an action is performed
   3069      * on the text view.  This will be called when the enter key is pressed,
   3070      * or when an action supplied to the IME is selected by the user.  Setting
   3071      * this means that the normal hard key event will not insert a newline
   3072      * into the text view, even if it is multi-line; holding down the ALT
   3073      * modifier will, however, allow the user to insert a newline character.
   3074      */
   3075     public void setOnEditorActionListener(OnEditorActionListener l) {
   3076         if (mInputContentType == null) {
   3077             mInputContentType = new InputContentType();
   3078         }
   3079         mInputContentType.onEditorActionListener = l;
   3080     }
   3081 
   3082     /**
   3083      * Called when an attached input method calls
   3084      * {@link InputConnection#performEditorAction(int)
   3085      * InputConnection.performEditorAction()}
   3086      * for this text view.  The default implementation will call your action
   3087      * listener supplied to {@link #setOnEditorActionListener}, or perform
   3088      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
   3089      * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
   3090      * EditorInfo.IME_ACTION_DONE}.
   3091      *
   3092      * <p>For backwards compatibility, if no IME options have been set and the
   3093      * text view would not normally advance focus on enter, then
   3094      * the NEXT and DONE actions received here will be turned into an enter
   3095      * key down/up pair to go through the normal key handling.
   3096      *
   3097      * @param actionCode The code of the action being performed.
   3098      *
   3099      * @see #setOnEditorActionListener
   3100      */
   3101     public void onEditorAction(int actionCode) {
   3102         final InputContentType ict = mInputContentType;
   3103         if (ict != null) {
   3104             if (ict.onEditorActionListener != null) {
   3105                 if (ict.onEditorActionListener.onEditorAction(this,
   3106                         actionCode, null)) {
   3107                     return;
   3108                 }
   3109             }
   3110 
   3111             // This is the handling for some default action.
   3112             // Note that for backwards compatibility we don't do this
   3113             // default handling if explicit ime options have not been given,
   3114             // instead turning this into the normal enter key codes that an
   3115             // app may be expecting.
   3116             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
   3117                 View v = focusSearch(FOCUS_DOWN);
   3118                 if (v != null) {
   3119                     if (!v.requestFocus(FOCUS_DOWN)) {
   3120                         throw new IllegalStateException("focus search returned a view " +
   3121                                 "that wasn't able to take focus!");
   3122                     }
   3123                 }
   3124                 return;
   3125 
   3126             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
   3127                 InputMethodManager imm = InputMethodManager.peekInstance();
   3128                 if (imm != null) {
   3129                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   3130                 }
   3131                 return;
   3132             }
   3133         }
   3134 
   3135         Handler h = getHandler();
   3136         if (h != null) {
   3137             long eventTime = SystemClock.uptimeMillis();
   3138             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
   3139                     new KeyEvent(eventTime, eventTime,
   3140                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
   3141                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   3142                     | KeyEvent.FLAG_EDITOR_ACTION)));
   3143             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
   3144                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
   3145                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
   3146                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   3147                     | KeyEvent.FLAG_EDITOR_ACTION)));
   3148         }
   3149     }
   3150 
   3151     /**
   3152      * Set the private content type of the text, which is the
   3153      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
   3154      * field that will be filled in when creating an input connection.
   3155      *
   3156      * @see #getPrivateImeOptions()
   3157      * @see EditorInfo#privateImeOptions
   3158      * @attr ref android.R.styleable#TextView_privateImeOptions
   3159      */
   3160     public void setPrivateImeOptions(String type) {
   3161         if (mInputContentType == null) mInputContentType = new InputContentType();
   3162         mInputContentType.privateImeOptions = type;
   3163     }
   3164 
   3165     /**
   3166      * Get the private type of the content.
   3167      *
   3168      * @see #setPrivateImeOptions(String)
   3169      * @see EditorInfo#privateImeOptions
   3170      */
   3171     public String getPrivateImeOptions() {
   3172         return mInputContentType != null
   3173                 ? mInputContentType.privateImeOptions : null;
   3174     }
   3175 
   3176     /**
   3177      * Set the extra input data of the text, which is the
   3178      * {@link EditorInfo#extras TextBoxAttribute.extras}
   3179      * Bundle that will be filled in when creating an input connection.  The
   3180      * given integer is the resource ID of an XML resource holding an
   3181      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
   3182      *
   3183      * @see #getInputExtras(boolean)
   3184      * @see EditorInfo#extras
   3185      * @attr ref android.R.styleable#TextView_editorExtras
   3186      */
   3187     public void setInputExtras(int xmlResId)
   3188             throws XmlPullParserException, IOException {
   3189         XmlResourceParser parser = getResources().getXml(xmlResId);
   3190         if (mInputContentType == null) mInputContentType = new InputContentType();
   3191         mInputContentType.extras = new Bundle();
   3192         getResources().parseBundleExtras(parser, mInputContentType.extras);
   3193     }
   3194 
   3195     /**
   3196      * Retrieve the input extras currently associated with the text view, which
   3197      * can be viewed as well as modified.
   3198      *
   3199      * @param create If true, the extras will be created if they don't already
   3200      * exist.  Otherwise, null will be returned if none have been created.
   3201      * @see #setInputExtras(int)View
   3202      * @see EditorInfo#extras
   3203      * @attr ref android.R.styleable#TextView_editorExtras
   3204      */
   3205     public Bundle getInputExtras(boolean create) {
   3206         if (mInputContentType == null) {
   3207             if (!create) return null;
   3208             mInputContentType = new InputContentType();
   3209         }
   3210         if (mInputContentType.extras == null) {
   3211             if (!create) return null;
   3212             mInputContentType.extras = new Bundle();
   3213         }
   3214         return mInputContentType.extras;
   3215     }
   3216 
   3217     /**
   3218      * Returns the error message that was set to be displayed with
   3219      * {@link #setError}, or <code>null</code> if no error was set
   3220      * or if it the error was cleared by the widget after user input.
   3221      */
   3222     public CharSequence getError() {
   3223         return mError;
   3224     }
   3225 
   3226     /**
   3227      * Sets the right-hand compound drawable of the TextView to the "error"
   3228      * icon and sets an error message that will be displayed in a popup when
   3229      * the TextView has focus.  The icon and error message will be reset to
   3230      * null when any key events cause changes to the TextView's text.  If the
   3231      * <code>error</code> is <code>null</code>, the error message and icon
   3232      * will be cleared.
   3233      */
   3234     @android.view.RemotableViewMethod
   3235     public void setError(CharSequence error) {
   3236         if (error == null) {
   3237             setError(null, null);
   3238         } else {
   3239             Drawable dr = getContext().getResources().
   3240                 getDrawable(com.android.internal.R.drawable.
   3241                             indicator_input_error);
   3242 
   3243             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
   3244             setError(error, dr);
   3245         }
   3246     }
   3247 
   3248     /**
   3249      * Sets the right-hand compound drawable of the TextView to the specified
   3250      * icon and sets an error message that will be displayed in a popup when
   3251      * the TextView has focus.  The icon and error message will be reset to
   3252      * null when any key events cause changes to the TextView's text.  The
   3253      * drawable must already have had {@link Drawable#setBounds} set on it.
   3254      * If the <code>error</code> is <code>null</code>, the error message will
   3255      * be cleared (and you should provide a <code>null</code> icon as well).
   3256      */
   3257     public void setError(CharSequence error, Drawable icon) {
   3258         error = TextUtils.stringOrSpannedString(error);
   3259 
   3260         mError = error;
   3261         mErrorWasChanged = true;
   3262         final Drawables dr = mDrawables;
   3263         if (dr != null) {
   3264             setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
   3265                                  icon, dr.mDrawableBottom);
   3266         } else {
   3267             setCompoundDrawables(null, null, icon, null);
   3268         }
   3269 
   3270         if (error == null) {
   3271             if (mPopup != null) {
   3272                 if (mPopup.isShowing()) {
   3273                     mPopup.dismiss();
   3274                 }
   3275 
   3276                 mPopup = null;
   3277             }
   3278         } else {
   3279             if (isFocused()) {
   3280                 showError();
   3281             }
   3282         }
   3283     }
   3284 
   3285     private void showError() {
   3286         if (getWindowToken() == null) {
   3287             mShowErrorAfterAttach = true;
   3288             return;
   3289         }
   3290 
   3291         if (mPopup == null) {
   3292             LayoutInflater inflater = LayoutInflater.from(getContext());
   3293             final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
   3294                     null);
   3295 
   3296             final float scale = getResources().getDisplayMetrics().density;
   3297             mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f),
   3298                     (int) (50 * scale + 0.5f));
   3299             mPopup.setFocusable(false);
   3300             // The user is entering text, so the input method is needed.  We
   3301             // don't want the popup to be displayed on top of it.
   3302             mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
   3303         }
   3304 
   3305         TextView tv = (TextView) mPopup.getContentView();
   3306         chooseSize(mPopup, mError, tv);
   3307         tv.setText(mError);
   3308 
   3309         mPopup.showAsDropDown(this, getErrorX(), getErrorY());
   3310         mPopup.fixDirection(mPopup.isAboveAnchor());
   3311     }
   3312 
   3313     private static class ErrorPopup extends PopupWindow {
   3314         private boolean mAbove = false;
   3315         private TextView mView;
   3316 
   3317         ErrorPopup(TextView v, int width, int height) {
   3318             super(v, width, height);
   3319             mView = v;
   3320         }
   3321 
   3322         void fixDirection(boolean above) {
   3323             mAbove = above;
   3324 
   3325             if (above) {
   3326                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
   3327             } else {
   3328                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
   3329             }
   3330         }
   3331 
   3332         @Override
   3333         public void update(int x, int y, int w, int h, boolean force) {
   3334             super.update(x, y, w, h, force);
   3335 
   3336             boolean above = isAboveAnchor();
   3337             if (above != mAbove) {
   3338                 fixDirection(above);
   3339             }
   3340         }
   3341     }
   3342 
   3343     /**
   3344      * Returns the Y offset to make the pointy top of the error point
   3345      * at the middle of the error icon.
   3346      */
   3347     private int getErrorX() {
   3348         /*
   3349          * The "25" is the distance between the point and the right edge
   3350          * of the background
   3351          */
   3352         final float scale = getResources().getDisplayMetrics().density;
   3353 
   3354         final Drawables dr = mDrawables;
   3355         return getWidth() - mPopup.getWidth()
   3356                 - getPaddingRight()
   3357                 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
   3358     }
   3359 
   3360     /**
   3361      * Returns the Y offset to make the pointy top of the error point
   3362      * at the bottom of the error icon.
   3363      */
   3364     private int getErrorY() {
   3365         /*
   3366          * Compound, not extended, because the icon is not clipped
   3367          * if the text height is smaller.
   3368          */
   3369         int vspace = mBottom - mTop -
   3370                      getCompoundPaddingBottom() - getCompoundPaddingTop();
   3371 
   3372         final Drawables dr = mDrawables;
   3373         int icontop = getCompoundPaddingTop()
   3374                 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
   3375 
   3376         /*
   3377          * The "2" is the distance between the point and the top edge
   3378          * of the background.
   3379          */
   3380 
   3381         return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
   3382                 - getHeight() - 2;
   3383     }
   3384 
   3385     private void hideError() {
   3386         if (mPopup != null) {
   3387             if (mPopup.isShowing()) {
   3388                 mPopup.dismiss();
   3389             }
   3390         }
   3391 
   3392         mShowErrorAfterAttach = false;
   3393     }
   3394 
   3395     private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
   3396         int wid = tv.getPaddingLeft() + tv.getPaddingRight();
   3397         int ht = tv.getPaddingTop() + tv.getPaddingBottom();
   3398 
   3399         /*
   3400          * Figure out how big the text would be if we laid it out to the
   3401          * full width of this view minus the border.
   3402          */
   3403         int cap = getWidth() - wid;
   3404         if (cap < 0) {
   3405             cap = 200; // We must not be measured yet -- setFrame() will fix it.
   3406         }
   3407 
   3408         Layout l = new StaticLayout(text, tv.getPaint(), cap,
   3409                                     Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
   3410         float max = 0;
   3411         for (int i = 0; i < l.getLineCount(); i++) {
   3412             max = Math.max(max, l.getLineWidth(i));
   3413         }
   3414 
   3415         /*
   3416          * Now set the popup size to be big enough for the text plus the border.
   3417          */
   3418         pop.setWidth(wid + (int) Math.ceil(max));
   3419         pop.setHeight(ht + l.getHeight());
   3420     }
   3421 
   3422 
   3423     @Override
   3424     protected boolean setFrame(int l, int t, int r, int b) {
   3425         boolean result = super.setFrame(l, t, r, b);
   3426 
   3427         if (mPopup != null) {
   3428             TextView tv = (TextView) mPopup.getContentView();
   3429             chooseSize(mPopup, mError, tv);
   3430             mPopup.update(this, getErrorX(), getErrorY(),
   3431                           mPopup.getWidth(), mPopup.getHeight());
   3432         }
   3433 
   3434         restartMarqueeIfNeeded();
   3435 
   3436         return result;
   3437     }
   3438 
   3439     private void restartMarqueeIfNeeded() {
   3440         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   3441             mRestartMarquee = false;
   3442             startMarquee();
   3443         }
   3444     }
   3445 
   3446     /**
   3447      * Sets the list of input filters that will be used if the buffer is
   3448      * Editable.  Has no effect otherwise.
   3449      *
   3450      * @attr ref android.R.styleable#TextView_maxLength
   3451      */
   3452     public void setFilters(InputFilter[] filters) {
   3453         if (filters == null) {
   3454             throw new IllegalArgumentException();
   3455         }
   3456 
   3457         mFilters = filters;
   3458 
   3459         if (mText instanceof Editable) {
   3460             setFilters((Editable) mText, filters);
   3461         }
   3462     }
   3463 
   3464     /**
   3465      * Sets the list of input filters on the specified Editable,
   3466      * and includes mInput in the list if it is an InputFilter.
   3467      */
   3468     private void setFilters(Editable e, InputFilter[] filters) {
   3469         if (mInput instanceof InputFilter) {
   3470             InputFilter[] nf = new InputFilter[filters.length + 1];
   3471 
   3472             System.arraycopy(filters, 0, nf, 0, filters.length);
   3473             nf[filters.length] = (InputFilter) mInput;
   3474 
   3475             e.setFilters(nf);
   3476         } else {
   3477             e.setFilters(filters);
   3478         }
   3479     }
   3480 
   3481     /**
   3482      * Returns the current list of input filters.
   3483      */
   3484     public InputFilter[] getFilters() {
   3485         return mFilters;
   3486     }
   3487 
   3488     /////////////////////////////////////////////////////////////////////////
   3489 
   3490     private int getVerticalOffset(boolean forceNormal) {
   3491         int voffset = 0;
   3492         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   3493 
   3494         Layout l = mLayout;
   3495         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   3496             l = mHintLayout;
   3497         }
   3498 
   3499         if (gravity != Gravity.TOP) {
   3500             int boxht;
   3501 
   3502             if (l == mHintLayout) {
   3503                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
   3504                         getCompoundPaddingBottom();
   3505             } else {
   3506                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
   3507                         getExtendedPaddingBottom();
   3508             }
   3509             int textht = l.getHeight();
   3510 
   3511             if (textht < boxht) {
   3512                 if (gravity == Gravity.BOTTOM)
   3513                     voffset = boxht - textht;
   3514                 else // (gravity == Gravity.CENTER_VERTICAL)
   3515                     voffset = (boxht - textht) >> 1;
   3516             }
   3517         }
   3518         return voffset;
   3519     }
   3520 
   3521     private int getBottomVerticalOffset(boolean forceNormal) {
   3522         int voffset = 0;
   3523         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   3524 
   3525         Layout l = mLayout;
   3526         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   3527             l = mHintLayout;
   3528         }
   3529 
   3530         if (gravity != Gravity.BOTTOM) {
   3531             int boxht;
   3532 
   3533             if (l == mHintLayout) {
   3534                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
   3535                         getCompoundPaddingBottom();
   3536             } else {
   3537                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
   3538                         getExtendedPaddingBottom();
   3539             }
   3540             int textht = l.getHeight();
   3541 
   3542             if (textht < boxht) {
   3543                 if (gravity == Gravity.TOP)
   3544                     voffset = boxht - textht;
   3545                 else // (gravity == Gravity.CENTER_VERTICAL)
   3546                     voffset = (boxht - textht) >> 1;
   3547             }
   3548         }
   3549         return voffset;
   3550     }
   3551 
   3552     private void invalidateCursorPath() {
   3553         if (mHighlightPathBogus) {
   3554             invalidateCursor();
   3555         } else {
   3556             synchronized (sTempRect) {
   3557                 /*
   3558                  * The reason for this concern about the thickness of the
   3559                  * cursor and doing the floor/ceil on the coordinates is that
   3560                  * some EditTexts (notably textfields in the Browser) have
   3561                  * anti-aliased text where not all the characters are
   3562                  * necessarily at integer-multiple locations.  This should
   3563                  * make sure the entire cursor gets invalidated instead of
   3564                  * sometimes missing half a pixel.
   3565                  */
   3566 
   3567                 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
   3568                 if (thick < 1.0f) {
   3569                     thick = 1.0f;
   3570                 }
   3571 
   3572                 thick /= 2;
   3573 
   3574                 mHighlightPath.computeBounds(sTempRect, false);
   3575 
   3576                 int left = getCompoundPaddingLeft();
   3577                 int top = getExtendedPaddingTop() + getVerticalOffset(true);
   3578 
   3579                 invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
   3580                            (int) FloatMath.floor(top + sTempRect.top - thick),
   3581                            (int) FloatMath.ceil(left + sTempRect.right + thick),
   3582                            (int) FloatMath.ceil(top + sTempRect.bottom + thick));
   3583             }
   3584         }
   3585     }
   3586 
   3587     private void invalidateCursor() {
   3588         int where = Selection.getSelectionEnd(mText);
   3589 
   3590         invalidateCursor(where, where, where);
   3591     }
   3592 
   3593     private void invalidateCursor(int a, int b, int c) {
   3594         if (mLayout == null) {
   3595             invalidate();
   3596         } else {
   3597             if (a >= 0 || b >= 0 || c >= 0) {
   3598                 int first = Math.min(Math.min(a, b), c);
   3599                 int last = Math.max(Math.max(a, b), c);
   3600 
   3601                 int line = mLayout.getLineForOffset(first);
   3602                 int top = mLayout.getLineTop(line);
   3603 
   3604                 // This is ridiculous, but the descent from the line above
   3605                 // can hang down into the line we really want to redraw,
   3606                 // so we have to invalidate part of the line above to make
   3607                 // sure everything that needs to be redrawn really is.
   3608                 // (But not the whole line above, because that would cause
   3609                 // the same problem with the descenders on the line above it!)
   3610                 if (line > 0) {
   3611                     top -= mLayout.getLineDescent(line - 1);
   3612                 }
   3613 
   3614                 int line2;
   3615 
   3616                 if (first == last)
   3617                     line2 = line;
   3618                 else
   3619                     line2 = mLayout.getLineForOffset(last);
   3620 
   3621                 int bottom = mLayout.getLineTop(line2 + 1);
   3622                 int voffset = getVerticalOffset(true);
   3623 
   3624                 int left = getCompoundPaddingLeft() + mScrollX;
   3625                 invalidate(left, top + voffset + getExtendedPaddingTop(),
   3626                            left + getWidth() - getCompoundPaddingLeft() -
   3627                            getCompoundPaddingRight(),
   3628                            bottom + voffset + getExtendedPaddingTop());
   3629             }
   3630         }
   3631     }
   3632 
   3633     private void registerForPreDraw() {
   3634         final ViewTreeObserver observer = getViewTreeObserver();
   3635         if (observer == null) {
   3636             return;
   3637         }
   3638 
   3639         if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
   3640             observer.addOnPreDrawListener(this);
   3641             mPreDrawState = PREDRAW_PENDING;
   3642         } else if (mPreDrawState == PREDRAW_DONE) {
   3643             mPreDrawState = PREDRAW_PENDING;
   3644         }
   3645 
   3646         // else state is PREDRAW_PENDING, so keep waiting.
   3647     }
   3648 
   3649     /**
   3650      * {@inheritDoc}
   3651      */
   3652     public boolean onPreDraw() {
   3653         if (mPreDrawState != PREDRAW_PENDING) {
   3654             return true;
   3655         }
   3656 
   3657         if (mLayout == null) {
   3658             assumeLayout();
   3659         }
   3660 
   3661         boolean changed = false;
   3662 
   3663         if (mMovement != null) {
   3664             int curs = Selection.getSelectionEnd(mText);
   3665 
   3666             /*
   3667              * TODO: This should really only keep the end in view if
   3668              * it already was before the text changed.  I'm not sure
   3669              * of a good way to tell from here if it was.
   3670              */
   3671             if (curs < 0 &&
   3672                   (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   3673                 curs = mText.length();
   3674             }
   3675 
   3676             if (curs >= 0) {
   3677                 changed = bringPointIntoView(curs);
   3678             }
   3679         } else {
   3680             changed = bringTextIntoView();
   3681         }
   3682 
   3683         mPreDrawState = PREDRAW_DONE;
   3684         return !changed;
   3685     }
   3686 
   3687     @Override
   3688     protected void onAttachedToWindow() {
   3689         super.onAttachedToWindow();
   3690 
   3691         mTemporaryDetach = false;
   3692 
   3693         if (mShowErrorAfterAttach) {
   3694             showError();
   3695             mShowErrorAfterAttach = false;
   3696         }
   3697     }
   3698 
   3699     @Override
   3700     protected void onDetachedFromWindow() {
   3701         super.onDetachedFromWindow();
   3702 
   3703         if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
   3704             final ViewTreeObserver observer = getViewTreeObserver();
   3705             if (observer != null) {
   3706                 observer.removeOnPreDrawListener(this);
   3707                 mPreDrawState = PREDRAW_NOT_REGISTERED;
   3708             }
   3709         }
   3710 
   3711         if (mError != null) {
   3712             hideError();
   3713         }
   3714     }
   3715 
   3716     @Override
   3717     protected boolean isPaddingOffsetRequired() {
   3718         return mShadowRadius != 0 || mDrawables != null;
   3719     }
   3720 
   3721     @Override
   3722     protected int getLeftPaddingOffset() {
   3723         return getCompoundPaddingLeft() - mPaddingLeft +
   3724                 (int) Math.min(0, mShadowDx - mShadowRadius);
   3725     }
   3726 
   3727     @Override
   3728     protected int getTopPaddingOffset() {
   3729         return (int) Math.min(0, mShadowDy - mShadowRadius);
   3730     }
   3731 
   3732     @Override
   3733     protected int getBottomPaddingOffset() {
   3734         return (int) Math.max(0, mShadowDy + mShadowRadius);
   3735     }
   3736 
   3737     @Override
   3738     protected int getRightPaddingOffset() {
   3739         return -(getCompoundPaddingRight() - mPaddingRight) +
   3740                 (int) Math.max(0, mShadowDx + mShadowRadius);
   3741     }
   3742 
   3743     @Override
   3744     protected boolean verifyDrawable(Drawable who) {
   3745         final boolean verified = super.verifyDrawable(who);
   3746         if (!verified && mDrawables != null) {
   3747             return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
   3748                     who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
   3749         }
   3750         return verified;
   3751     }
   3752 
   3753     @Override
   3754     public void invalidateDrawable(Drawable drawable) {
   3755         if (verifyDrawable(drawable)) {
   3756             final Rect dirty = drawable.getBounds();
   3757             int scrollX = mScrollX;
   3758             int scrollY = mScrollY;
   3759 
   3760             // IMPORTANT: The coordinates below are based on the coordinates computed
   3761             // for each compound drawable in onDraw(). Make sure to update each section
   3762             // accordingly.
   3763             final TextView.Drawables drawables = mDrawables;
   3764             if (drawables != null) {
   3765                 if (drawable == drawables.mDrawableLeft) {
   3766                     final int compoundPaddingTop = getCompoundPaddingTop();
   3767                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   3768                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   3769 
   3770                     scrollX += mPaddingLeft;
   3771                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
   3772                 } else if (drawable == drawables.mDrawableRight) {
   3773                     final int compoundPaddingTop = getCompoundPaddingTop();
   3774                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   3775                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   3776 
   3777                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
   3778                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
   3779                 } else if (drawable == drawables.mDrawableTop) {
   3780                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   3781                     final int compoundPaddingRight = getCompoundPaddingRight();
   3782                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   3783 
   3784                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
   3785                     scrollY += mPaddingTop;
   3786                 } else if (drawable == drawables.mDrawableBottom) {
   3787                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   3788                     final int compoundPaddingRight = getCompoundPaddingRight();
   3789                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   3790 
   3791                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
   3792                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
   3793                 }
   3794             }
   3795 
   3796             invalidate(dirty.left + scrollX, dirty.top + scrollY,
   3797                     dirty.right + scrollX, dirty.bottom + scrollY);
   3798         }
   3799     }
   3800 
   3801     @Override
   3802     protected void onDraw(Canvas canvas) {
   3803         restartMarqueeIfNeeded();
   3804 
   3805         // Draw the background for this view
   3806         super.onDraw(canvas);
   3807 
   3808         final int compoundPaddingLeft = getCompoundPaddingLeft();
   3809         final int compoundPaddingTop = getCompoundPaddingTop();
   3810         final int compoundPaddingRight = getCompoundPaddingRight();
   3811         final int compoundPaddingBottom = getCompoundPaddingBottom();
   3812         final int scrollX = mScrollX;
   3813         final int scrollY = mScrollY;
   3814         final int right = mRight;
   3815         final int left = mLeft;
   3816         final int bottom = mBottom;
   3817         final int top = mTop;
   3818 
   3819         final Drawables dr = mDrawables;
   3820         if (dr != null) {
   3821             /*
   3822              * Compound, not extended, because the icon is not clipped
   3823              * if the text height is smaller.
   3824              */
   3825 
   3826             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
   3827             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
   3828 
   3829             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3830             // Make sure to update invalidateDrawable() when changing this code.
   3831             if (dr.mDrawableLeft != null) {
   3832                 canvas.save();
   3833                 canvas.translate(scrollX + mPaddingLeft,
   3834                                  scrollY + compoundPaddingTop +
   3835                                  (vspace - dr.mDrawableHeightLeft) / 2);
   3836                 dr.mDrawableLeft.draw(canvas);
   3837                 canvas.restore();
   3838             }
   3839 
   3840             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3841             // Make sure to update invalidateDrawable() when changing this code.
   3842             if (dr.mDrawableRight != null) {
   3843                 canvas.save();
   3844                 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
   3845                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
   3846                 dr.mDrawableRight.draw(canvas);
   3847                 canvas.restore();
   3848             }
   3849 
   3850             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3851             // Make sure to update invalidateDrawable() when changing this code.
   3852             if (dr.mDrawableTop != null) {
   3853                 canvas.save();
   3854                 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
   3855                         scrollY + mPaddingTop);
   3856                 dr.mDrawableTop.draw(canvas);
   3857                 canvas.restore();
   3858             }
   3859 
   3860             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   3861             // Make sure to update invalidateDrawable() when changing this code.
   3862             if (dr.mDrawableBottom != null) {
   3863                 canvas.save();
   3864                 canvas.translate(scrollX + compoundPaddingLeft +
   3865                         (hspace - dr.mDrawableWidthBottom) / 2,
   3866                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
   3867                 dr.mDrawableBottom.draw(canvas);
   3868                 canvas.restore();
   3869             }
   3870         }
   3871 
   3872         if (mPreDrawState == PREDRAW_DONE) {
   3873             final ViewTreeObserver observer = getViewTreeObserver();
   3874             if (observer != null) {
   3875                 observer.removeOnPreDrawListener(this);
   3876                 mPreDrawState = PREDRAW_NOT_REGISTERED;
   3877             }
   3878         }
   3879 
   3880         int color = mCurTextColor;
   3881 
   3882         if (mLayout == null) {
   3883             assumeLayout();
   3884         }
   3885 
   3886         Layout layout = mLayout;
   3887         int cursorcolor = color;
   3888 
   3889         if (mHint != null && mText.length() == 0) {
   3890             if (mHintTextColor != null) {
   3891                 color = mCurHintTextColor;
   3892             }
   3893 
   3894             layout = mHintLayout;
   3895         }
   3896 
   3897         mTextPaint.setColor(color);
   3898         mTextPaint.drawableState = getDrawableState();
   3899 
   3900         canvas.save();
   3901         /*  Would be faster if we didn't have to do this. Can we chop the
   3902             (displayable) text so that we don't need to do this ever?
   3903         */
   3904 
   3905         int extendedPaddingTop = getExtendedPaddingTop();
   3906         int extendedPaddingBottom = getExtendedPaddingBottom();
   3907 
   3908         float clipLeft = compoundPaddingLeft + scrollX;
   3909         float clipTop = extendedPaddingTop + scrollY;
   3910         float clipRight = right - left - compoundPaddingRight + scrollX;
   3911         float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
   3912 
   3913         if (mShadowRadius != 0) {
   3914             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
   3915             clipRight += Math.max(0, mShadowDx + mShadowRadius);
   3916 
   3917             clipTop += Math.min(0, mShadowDy - mShadowRadius);
   3918             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
   3919         }
   3920 
   3921         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
   3922 
   3923         int voffsetText = 0;
   3924         int voffsetCursor = 0;
   3925 
   3926         // translate in by our padding
   3927         {
   3928             /* shortcircuit calling getVerticaOffset() */
   3929             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   3930                 voffsetText = getVerticalOffset(false);
   3931                 voffsetCursor = getVerticalOffset(true);
   3932             }
   3933             canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
   3934         }
   3935 
   3936         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   3937             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
   3938                     (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
   3939                 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
   3940                         getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
   3941             }
   3942 
   3943             if (mMarquee != null && mMarquee.isRunning()) {
   3944                 canvas.translate(-mMarquee.mScroll, 0.0f);
   3945             }
   3946         }
   3947 
   3948         Path highlight = null;
   3949         int selStart = -1, selEnd = -1;
   3950 
   3951         //  If there is no movement method, then there can be no selection.
   3952         //  Check that first and attempt to skip everything having to do with
   3953         //  the cursor.
   3954         //  XXX This is not strictly true -- a program could set the
   3955         //  selection manually if it really wanted to.
   3956         if (mMovement != null && (isFocused() || isPressed())) {
   3957             selStart = Selection.getSelectionStart(mText);
   3958             selEnd = Selection.getSelectionEnd(mText);
   3959 
   3960             if (mCursorVisible && selStart >= 0 && isEnabled()) {
   3961                 if (mHighlightPath == null)
   3962                     mHighlightPath = new Path();
   3963 
   3964                 if (selStart == selEnd) {
   3965                     if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
   3966                         if (mHighlightPathBogus) {
   3967                             mHighlightPath.reset();
   3968                             mLayout.getCursorPath(selStart, mHighlightPath, mText);
   3969                             mHighlightPathBogus = false;
   3970                         }
   3971 
   3972                         // XXX should pass to skin instead of drawing directly
   3973                         mHighlightPaint.setColor(cursorcolor);
   3974                         mHighlightPaint.setStyle(Paint.Style.STROKE);
   3975 
   3976                         highlight = mHighlightPath;
   3977                     }
   3978                 } else {
   3979                     if (mHighlightPathBogus) {
   3980                         mHighlightPath.reset();
   3981                         mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   3982                         mHighlightPathBogus = false;
   3983                     }
   3984 
   3985                     // XXX should pass to skin instead of drawing directly
   3986                     mHighlightPaint.setColor(mHighlightColor);
   3987                     mHighlightPaint.setStyle(Paint.Style.FILL);
   3988 
   3989                     highlight = mHighlightPath;
   3990                 }
   3991             }
   3992         }
   3993 
   3994         /*  Comment out until we decide what to do about animations
   3995         boolean isLinearTextOn = false;
   3996         if (currentTransformation != null) {
   3997             isLinearTextOn = mTextPaint.isLinearTextOn();
   3998             Matrix m = currentTransformation.getMatrix();
   3999             if (!m.isIdentity()) {
   4000                 // mTextPaint.setLinearTextOn(true);
   4001             }
   4002         }
   4003         */
   4004 
   4005         final InputMethodState ims = mInputMethodState;
   4006         if (ims != null && ims.mBatchEditNesting == 0) {
   4007             InputMethodManager imm = InputMethodManager.peekInstance();
   4008             if (imm != null) {
   4009                 if (imm.isActive(this)) {
   4010                     boolean reported = false;
   4011                     if (ims.mContentChanged || ims.mSelectionModeChanged) {
   4012                         // We are in extract mode and the content has changed
   4013                         // in some way... just report complete new text to the
   4014                         // input method.
   4015                         reported = reportExtractedText();
   4016                     }
   4017                     if (!reported && highlight != null) {
   4018                         int candStart = -1;
   4019                         int candEnd = -1;
   4020                         if (mText instanceof Spannable) {
   4021                             Spannable sp = (Spannable)mText;
   4022                             candStart = EditableInputConnection.getComposingSpanStart(sp);
   4023                             candEnd = EditableInputConnection.getComposingSpanEnd(sp);
   4024                         }
   4025                         imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
   4026                     }
   4027                 }
   4028 
   4029                 if (imm.isWatchingCursor(this) && highlight != null) {
   4030                     highlight.computeBounds(ims.mTmpRectF, true);
   4031                     ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
   4032 
   4033                     canvas.getMatrix().mapPoints(ims.mTmpOffset);
   4034                     ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
   4035 
   4036                     ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
   4037 
   4038                     ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
   4039                             (int)(ims.mTmpRectF.top + 0.5),
   4040                             (int)(ims.mTmpRectF.right + 0.5),
   4041                             (int)(ims.mTmpRectF.bottom + 0.5));
   4042 
   4043                     imm.updateCursor(this,
   4044                             ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
   4045                             ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
   4046                 }
   4047             }
   4048         }
   4049 
   4050         layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
   4051 
   4052         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
   4053             canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
   4054             layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
   4055         }
   4056 
   4057         /*  Comment out until we decide what to do about animations
   4058         if (currentTransformation != null) {
   4059             mTextPaint.setLinearTextOn(isLinearTextOn);
   4060         }
   4061         */
   4062 
   4063         canvas.restore();
   4064     }
   4065 
   4066     @Override
   4067     public void getFocusedRect(Rect r) {
   4068         if (mLayout == null) {
   4069             super.getFocusedRect(r);
   4070             return;
   4071         }
   4072 
   4073         int sel = getSelectionEnd();
   4074         if (sel < 0) {
   4075             super.getFocusedRect(r);
   4076             return;
   4077         }
   4078 
   4079         int line = mLayout.getLineForOffset(sel);
   4080         r.top = mLayout.getLineTop(line);
   4081         r.bottom = mLayout.getLineBottom(line);
   4082 
   4083         r.left = (int) mLayout.getPrimaryHorizontal(sel);
   4084         r.right = r.left + 1;
   4085 
   4086         // Adjust for padding and gravity.
   4087         int paddingLeft = getCompoundPaddingLeft();
   4088         int paddingTop = getExtendedPaddingTop();
   4089         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4090             paddingTop += getVerticalOffset(false);
   4091         }
   4092         r.offset(paddingLeft, paddingTop);
   4093     }
   4094 
   4095     /**
   4096      * Return the number of lines of text, or 0 if the internal Layout has not
   4097      * been built.
   4098      */
   4099     public int getLineCount() {
   4100         return mLayout != null ? mLayout.getLineCount() : 0;
   4101     }
   4102 
   4103     /**
   4104      * Return the baseline for the specified line (0...getLineCount() - 1)
   4105      * If bounds is not null, return the top, left, right, bottom extents
   4106      * of the specified line in it. If the internal Layout has not been built,
   4107      * return 0 and set bounds to (0, 0, 0, 0)
   4108      * @param line which line to examine (0..getLineCount() - 1)
   4109      * @param bounds Optional. If not null, it returns the extent of the line
   4110      * @return the Y-coordinate of the baseline
   4111      */
   4112     public int getLineBounds(int line, Rect bounds) {
   4113         if (mLayout == null) {
   4114             if (bounds != null) {
   4115                 bounds.set(0, 0, 0, 0);
   4116             }
   4117             return 0;
   4118         }
   4119         else {
   4120             int baseline = mLayout.getLineBounds(line, bounds);
   4121 
   4122             int voffset = getExtendedPaddingTop();
   4123             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4124                 voffset += getVerticalOffset(true);
   4125             }
   4126             if (bounds != null) {
   4127                 bounds.offset(getCompoundPaddingLeft(), voffset);
   4128             }
   4129             return baseline + voffset;
   4130         }
   4131     }
   4132 
   4133     @Override
   4134     public int getBaseline() {
   4135         if (mLayout == null) {
   4136             return super.getBaseline();
   4137         }
   4138 
   4139         int voffset = 0;
   4140         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   4141             voffset = getVerticalOffset(true);
   4142         }
   4143 
   4144         return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
   4145     }
   4146 
   4147     @Override
   4148     public boolean onKeyDown(int keyCode, KeyEvent event) {
   4149         int which = doKeyDown(keyCode, event, null);
   4150         if (which == 0) {
   4151             // Go through default dispatching.
   4152             return super.onKeyDown(keyCode, event);
   4153         }
   4154 
   4155         return true;
   4156     }
   4157 
   4158     @Override
   4159     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   4160         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
   4161 
   4162         int which = doKeyDown(keyCode, down, event);
   4163         if (which == 0) {
   4164             // Go through default dispatching.
   4165             return super.onKeyMultiple(keyCode, repeatCount, event);
   4166         }
   4167         if (which == -1) {
   4168             // Consumed the whole thing.
   4169             return true;
   4170         }
   4171 
   4172         repeatCount--;
   4173 
   4174         // We are going to dispatch the remaining events to either the input
   4175         // or movement method.  To do this, we will just send a repeated stream
   4176         // of down and up events until we have done the complete repeatCount.
   4177         // It would be nice if those interfaces had an onKeyMultiple() method,
   4178         // but adding that is a more complicated change.
   4179         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
   4180         if (which == 1) {
   4181             mInput.onKeyUp(this, (Editable)mText, keyCode, up);
   4182             while (--repeatCount > 0) {
   4183                 mInput.onKeyDown(this, (Editable)mText, keyCode, down);
   4184                 mInput.onKeyUp(this, (Editable)mText, keyCode, up);
   4185             }
   4186             if (mError != null && !mErrorWasChanged) {
   4187                 setError(null, null);
   4188             }
   4189 
   4190         } else if (which == 2) {
   4191             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   4192             while (--repeatCount > 0) {
   4193                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
   4194                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   4195             }
   4196         }
   4197 
   4198         return true;
   4199     }
   4200 
   4201     /**
   4202      * Returns true if pressing ENTER in this field advances focus instead
   4203      * of inserting the character.  This is true mostly in single-line fields,
   4204      * but also in mail addresses and subjects which will display on multiple
   4205      * lines but where it doesn't make sense to insert newlines.
   4206      */
   4207     private boolean shouldAdvanceFocusOnEnter() {
   4208         if (mInput == null) {
   4209             return false;
   4210         }
   4211 
   4212         if (mSingleLine) {
   4213             return true;
   4214         }
   4215 
   4216         if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
   4217             int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
   4218 
   4219             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
   4220                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
   4221                 return true;
   4222             }
   4223         }
   4224 
   4225         return false;
   4226     }
   4227 
   4228     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
   4229         if (!isEnabled()) {
   4230             return 0;
   4231         }
   4232 
   4233         switch (keyCode) {
   4234             case KeyEvent.KEYCODE_ENTER:
   4235                 // If ALT modifier is held, then we always insert a
   4236                 // newline character.
   4237                 if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
   4238 
   4239                     // When mInputContentType is set, we know that we are
   4240                     // running in a "modern" cupcake environment, so don't need
   4241                     // to worry about the application trying to capture
   4242                     // enter key events.
   4243                     if (mInputContentType != null) {
   4244 
   4245                         // If there is an action listener, given them a
   4246                         // chance to consume the event.
   4247                         if (mInputContentType.onEditorActionListener != null &&
   4248                                 mInputContentType.onEditorActionListener.onEditorAction(
   4249                                 this, EditorInfo.IME_NULL, event)) {
   4250                             mInputContentType.enterDown = true;
   4251                             // We are consuming the enter key for them.
   4252                             return -1;
   4253                         }
   4254                     }
   4255 
   4256                     // If our editor should move focus when enter is pressed, or
   4257                     // this is a generated event from an IME action button, then
   4258                     // don't let it be inserted into the text.
   4259                     if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
   4260                             || shouldAdvanceFocusOnEnter()) {
   4261                         return -1;
   4262                     }
   4263                 }
   4264                 break;
   4265 
   4266             case KeyEvent.KEYCODE_DPAD_CENTER:
   4267                 if (shouldAdvanceFocusOnEnter()) {
   4268                     return 0;
   4269                 }
   4270         }
   4271 
   4272         if (mInput != null) {
   4273             /*
   4274              * Keep track of what the error was before doing the input
   4275              * so that if an input filter changed the error, we leave
   4276              * that error showing.  Otherwise, we take down whatever
   4277              * error was showing when the user types something.
   4278              */
   4279             mErrorWasChanged = false;
   4280 
   4281             boolean doDown = true;
   4282             if (otherEvent != null) {
   4283                 try {
   4284                     beginBatchEdit();
   4285                     boolean handled = mInput.onKeyOther(this, (Editable) mText,
   4286                             otherEvent);
   4287                     if (mError != null && !mErrorWasChanged) {
   4288                         setError(null, null);
   4289                     }
   4290                     doDown = false;
   4291                     if (handled) {
   4292                         return -1;
   4293                     }
   4294                 } catch (AbstractMethodError e) {
   4295                     // onKeyOther was added after 1.0, so if it isn't
   4296                     // implemented we need to try to dispatch as a regular down.
   4297                 } finally {
   4298                     endBatchEdit();
   4299                 }
   4300             }
   4301 
   4302             if (doDown) {
   4303                 beginBatchEdit();
   4304                 if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
   4305                     endBatchEdit();
   4306                     if (mError != null && !mErrorWasChanged) {
   4307                         setError(null, null);
   4308                     }
   4309                     return 1;
   4310                 }
   4311                 endBatchEdit();
   4312             }
   4313         }
   4314 
   4315         // bug 650865: sometimes we get a key event before a layout.
   4316         // don't try to move around if we don't know the layout.
   4317 
   4318         if (mMovement != null && mLayout != null) {
   4319             boolean doDown = true;
   4320             if (otherEvent != null) {
   4321                 try {
   4322                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
   4323                             otherEvent);
   4324                     doDown = false;
   4325                     if (handled) {
   4326                         return -1;
   4327                     }
   4328                 } catch (AbstractMethodError e) {
   4329                     // onKeyOther was added after 1.0, so if it isn't
   4330                     // implemented we need to try to dispatch as a regular down.
   4331                 }
   4332             }
   4333             if (doDown) {
   4334                 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
   4335                     return 2;
   4336             }
   4337         }
   4338 
   4339         return 0;
   4340     }
   4341 
   4342     @Override
   4343     public boolean onKeyUp(int keyCode, KeyEvent event) {
   4344         if (!isEnabled()) {
   4345             return super.onKeyUp(keyCode, event);
   4346         }
   4347 
   4348         switch (keyCode) {
   4349             case KeyEvent.KEYCODE_DPAD_CENTER:
   4350                 /*
   4351                  * If there is a click listener, just call through to
   4352                  * super, which will invoke it.
   4353                  *
   4354                  * If there isn't a click listener, try to show the soft
   4355                  * input method.  (It will also
   4356                  * call performClick(), but that won't do anything in
   4357                  * this case.)
   4358                  */
   4359                 if (mOnClickListener == null) {
   4360                     if (mMovement != null && mText instanceof Editable
   4361                             && mLayout != null && onCheckIsTextEditor()) {
   4362                         InputMethodManager imm = (InputMethodManager)
   4363                                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
   4364                         imm.showSoftInput(this, 0);
   4365                     }
   4366                 }
   4367                 return super.onKeyUp(keyCode, event);
   4368 
   4369             case KeyEvent.KEYCODE_ENTER:
   4370                 if (mInputContentType != null
   4371                         && mInputContentType.onEditorActionListener != null
   4372                         && mInputContentType.enterDown) {
   4373                     mInputContentType.enterDown = false;
   4374                     if (mInputContentType.onEditorActionListener.onEditorAction(
   4375                             this, EditorInfo.IME_NULL, event)) {
   4376                         return true;
   4377                     }
   4378                 }
   4379 
   4380                 if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
   4381                         || shouldAdvanceFocusOnEnter()) {
   4382                     /*
   4383                      * If there is a click listener, just call through to
   4384                      * super, which will invoke it.
   4385                      *
   4386                      * If there isn't a click listener, try to advance focus,
   4387                      * but still call through to super, which will reset the
   4388                      * pressed state and longpress state.  (It will also
   4389                      * call performClick(), but that won't do anything in
   4390                      * this case.)
   4391                      */
   4392                     if (mOnClickListener == null) {
   4393                         View v = focusSearch(FOCUS_DOWN);
   4394 
   4395                         if (v != null) {
   4396                             if (!v.requestFocus(FOCUS_DOWN)) {
   4397                                 throw new IllegalStateException("focus search returned a view " +
   4398                                         "that wasn't able to take focus!");
   4399                             }
   4400 
   4401                             /*
   4402                              * Return true because we handled the key; super
   4403                              * will return false because there was no click
   4404                              * listener.
   4405                              */
   4406                             super.onKeyUp(keyCode, event);
   4407                             return true;
   4408                         } else if ((event.getFlags()
   4409                                 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
   4410                             // No target for next focus, but make sure the IME
   4411                             // if this came from it.
   4412                             InputMethodManager imm = InputMethodManager.peekInstance();
   4413                             if (imm != null) {
   4414                                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
   4415                             }
   4416                         }
   4417                     }
   4418 
   4419                     return super.onKeyUp(keyCode, event);
   4420                 }
   4421         }
   4422 
   4423         if (mInput != null)
   4424             if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
   4425                 return true;
   4426 
   4427         if (mMovement != null && mLayout != null)
   4428             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
   4429                 return true;
   4430 
   4431         return super.onKeyUp(keyCode, event);
   4432     }
   4433 
   4434     @Override public boolean onCheckIsTextEditor() {
   4435         return mInputType != EditorInfo.TYPE_NULL;
   4436     }
   4437 
   4438     @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   4439         if (onCheckIsTextEditor()) {
   4440             if (mInputMethodState == null) {
   4441                 mInputMethodState = new InputMethodState();
   4442             }
   4443             outAttrs.inputType = mInputType;
   4444             if (mInputContentType != null) {
   4445                 outAttrs.imeOptions = mInputContentType.imeOptions;
   4446                 outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
   4447                 outAttrs.actionLabel = mInputContentType.imeActionLabel;
   4448                 outAttrs.actionId = mInputContentType.imeActionId;
   4449                 outAttrs.extras = mInputContentType.extras;
   4450             } else {
   4451                 outAttrs.imeOptions = EditorInfo.IME_NULL;
   4452             }
   4453             if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
   4454                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
   4455                 if (focusSearch(FOCUS_DOWN) != null) {
   4456                     // An action has not been set, but the enter key will move to
   4457                     // the next focus, so set the action to that.
   4458                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
   4459                 } else {
   4460                     // An action has not been set, and there is no focus to move
   4461                     // to, so let's just supply a "done" action.
   4462                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
   4463                 }
   4464                 if (!shouldAdvanceFocusOnEnter()) {
   4465                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   4466                 }
   4467             }
   4468             if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
   4469                     | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
   4470                     == (InputType.TYPE_CLASS_TEXT
   4471                             | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
   4472                 // Multi-line text editors should always show an enter key.
   4473                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   4474             }
   4475             outAttrs.hintText = mHint;
   4476             if (mText instanceof Editable) {
   4477                 InputConnection ic = new EditableInputConnection(this);
   4478                 outAttrs.initialSelStart = Selection.getSelectionStart(mText);
   4479                 outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
   4480                 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
   4481                 return ic;
   4482             }
   4483         }
   4484         return null;
   4485     }
   4486 
   4487     /**
   4488      * If this TextView contains editable content, extract a portion of it
   4489      * based on the information in <var>request</var> in to <var>outText</var>.
   4490      * @return Returns true if the text was successfully extracted, else false.
   4491      */
   4492     public boolean extractText(ExtractedTextRequest request,
   4493             ExtractedText outText) {
   4494         return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
   4495                 EXTRACT_UNKNOWN, outText);
   4496     }
   4497 
   4498     static final int EXTRACT_NOTHING = -2;
   4499     static final int EXTRACT_UNKNOWN = -1;
   4500 
   4501     boolean extractTextInternal(ExtractedTextRequest request,
   4502             int partialStartOffset, int partialEndOffset, int delta,
   4503             ExtractedText outText) {
   4504         final CharSequence content = mText;
   4505         if (content != null) {
   4506             if (partialStartOffset != EXTRACT_NOTHING) {
   4507                 final int N = content.length();
   4508                 if (partialStartOffset < 0) {
   4509                     outText.partialStartOffset = outText.partialEndOffset = -1;
   4510                     partialStartOffset = 0;
   4511                     partialEndOffset = N;
   4512                 } else {
   4513                     // Adjust offsets to ensure we contain full spans.
   4514                     if (content instanceof Spanned) {
   4515                         Spanned spanned = (Spanned)content;
   4516                         Object[] spans = spanned.getSpans(partialStartOffset,
   4517                                 partialEndOffset, ParcelableSpan.class);
   4518                         int i = spans.length;
   4519                         while (i > 0) {
   4520                             i--;
   4521                             int j = spanned.getSpanStart(spans[i]);
   4522                             if (j < partialStartOffset) partialStartOffset = j;
   4523                             j = spanned.getSpanEnd(spans[i]);
   4524                             if (j > partialEndOffset) partialEndOffset = j;
   4525                         }
   4526                     }
   4527                     outText.partialStartOffset = partialStartOffset;
   4528                     outText.partialEndOffset = partialEndOffset;
   4529                     // Now use the delta to determine the actual amount of text
   4530                     // we need.
   4531                     partialEndOffset += delta;
   4532                     if (partialStartOffset > N) {
   4533                         partialStartOffset = N;
   4534                     } else if (partialStartOffset < 0) {
   4535                         partialStartOffset = 0;
   4536                     }
   4537                     if (partialEndOffset > N) {
   4538                         partialEndOffset = N;
   4539                     } else if (partialEndOffset < 0) {
   4540                         partialEndOffset = 0;
   4541                     }
   4542                 }
   4543                 if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
   4544                     outText.text = content.subSequence(partialStartOffset,
   4545                             partialEndOffset);
   4546                 } else {
   4547                     outText.text = TextUtils.substring(content, partialStartOffset,
   4548                             partialEndOffset);
   4549                 }
   4550             }
   4551             outText.flags = 0;
   4552             if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
   4553                 outText.flags |= ExtractedText.FLAG_SELECTING;
   4554             }
   4555             if (mSingleLine) {
   4556                 outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
   4557             }
   4558             outText.startOffset = 0;
   4559             outText.selectionStart = Selection.getSelectionStart(content);
   4560             outText.selectionEnd = Selection.getSelectionEnd(content);
   4561             return true;
   4562         }
   4563         return false;
   4564     }
   4565 
   4566     boolean reportExtractedText() {
   4567         final InputMethodState ims = mInputMethodState;
   4568         if (ims != null) {
   4569             final boolean contentChanged = ims.mContentChanged;
   4570             if (contentChanged || ims.mSelectionModeChanged) {
   4571                 ims.mContentChanged = false;
   4572                 ims.mSelectionModeChanged = false;
   4573                 final ExtractedTextRequest req = mInputMethodState.mExtracting;
   4574                 if (req != null) {
   4575                     InputMethodManager imm = InputMethodManager.peekInstance();
   4576                     if (imm != null) {
   4577                         if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
   4578                                 + ims.mChangedStart + " end=" + ims.mChangedEnd
   4579                                 + " delta=" + ims.mChangedDelta);
   4580                         if (ims.mChangedStart < 0 && !contentChanged) {
   4581                             ims.mChangedStart = EXTRACT_NOTHING;
   4582                         }
   4583                         if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
   4584                                 ims.mChangedDelta, ims.mTmpExtracted)) {
   4585                             if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
   4586                                     + ims.mTmpExtracted.partialStartOffset
   4587                                     + " end=" + ims.mTmpExtracted.partialEndOffset
   4588                                     + ": " + ims.mTmpExtracted.text);
   4589                             imm.updateExtractedText(this, req.token,
   4590                                     mInputMethodState.mTmpExtracted);
   4591                             return true;
   4592                         }
   4593                     }
   4594                 }
   4595             }
   4596         }
   4597         return false;
   4598     }
   4599 
   4600     /**
   4601      * This is used to remove all style-impacting spans from text before new
   4602      * extracted text is being replaced into it, so that we don't have any
   4603      * lingering spans applied during the replace.
   4604      */
   4605     static void removeParcelableSpans(Spannable spannable, int start, int end) {
   4606         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
   4607         int i = spans.length;
   4608         while (i > 0) {
   4609             i--;
   4610             spannable.removeSpan(spans[i]);
   4611         }
   4612     }
   4613 
   4614     /**
   4615      * Apply to this text view the given extracted text, as previously
   4616      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
   4617      */
   4618     public void setExtractedText(ExtractedText text) {
   4619         Editable content = getEditableText();
   4620         if (text.text != null) {
   4621             if (content == null) {
   4622                 setText(text.text, TextView.BufferType.EDITABLE);
   4623             } else if (text.partialStartOffset < 0) {
   4624                 removeParcelableSpans(content, 0, content.length());
   4625                 content.replace(0, content.length(), text.text);
   4626             } else {
   4627                 final int N = content.length();
   4628                 int start = text.partialStartOffset;
   4629                 if (start > N) start = N;
   4630                 int end = text.partialEndOffset;
   4631                 if (end > N) end = N;
   4632                 removeParcelableSpans(content, start, end);
   4633                 content.replace(start, end, text.text);
   4634             }
   4635         }
   4636 
   4637         // Now set the selection position...  make sure it is in range, to
   4638         // avoid crashes.  If this is a partial update, it is possible that
   4639         // the underlying text may have changed, causing us problems here.
   4640         // Also we just don't want to trust clients to do the right thing.
   4641         Spannable sp = (Spannable)getText();
   4642         final int N = sp.length();
   4643         int start = text.selectionStart;
   4644         if (start < 0) start = 0;
   4645         else if (start > N) start = N;
   4646         int end = text.selectionEnd;
   4647         if (end < 0) end = 0;
   4648         else if (end > N) end = N;
   4649         Selection.setSelection(sp, start, end);
   4650 
   4651         // Finally, update the selection mode.
   4652         if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
   4653             MetaKeyKeyListener.startSelecting(this, sp);
   4654         } else {
   4655             MetaKeyKeyListener.stopSelecting(this, sp);
   4656         }
   4657     }
   4658 
   4659     /**
   4660      * @hide
   4661      */
   4662     public void setExtracting(ExtractedTextRequest req) {
   4663         if (mInputMethodState != null) {
   4664             mInputMethodState.mExtracting = req;
   4665         }
   4666     }
   4667 
   4668     /**
   4669      * Called by the framework in response to a text completion from
   4670      * the current input method, provided by it calling
   4671      * {@link InputConnection#commitCompletion
   4672      * InputConnection.commitCompletion()}.  The default implementation does
   4673      * nothing; text views that are supporting auto-completion should override
   4674      * this to do their desired behavior.
   4675      *
   4676      * @param text The auto complete text the user has selected.
   4677      */
   4678     public void onCommitCompletion(CompletionInfo text) {
   4679     }
   4680 
   4681     public void beginBatchEdit() {
   4682         final InputMethodState ims = mInputMethodState;
   4683         if (ims != null) {
   4684             int nesting = ++ims.mBatchEditNesting;
   4685             if (nesting == 1) {
   4686                 ims.mCursorChanged = false;
   4687                 ims.mChangedDelta = 0;
   4688                 if (ims.mContentChanged) {
   4689                     // We already have a pending change from somewhere else,
   4690                     // so turn this into a full update.
   4691                     ims.mChangedStart = 0;
   4692                     ims.mChangedEnd = mText.length();
   4693                 } else {
   4694                     ims.mChangedStart = EXTRACT_UNKNOWN;
   4695                     ims.mChangedEnd = EXTRACT_UNKNOWN;
   4696                     ims.mContentChanged = false;
   4697                 }
   4698                 onBeginBatchEdit();
   4699             }
   4700         }
   4701     }
   4702 
   4703     public void endBatchEdit() {
   4704         final InputMethodState ims = mInputMethodState;
   4705         if (ims != null) {
   4706             int nesting = --ims.mBatchEditNesting;
   4707             if (nesting == 0) {
   4708                 finishBatchEdit(ims);
   4709             }
   4710         }
   4711     }
   4712 
   4713     void ensureEndedBatchEdit() {
   4714         final InputMethodState ims = mInputMethodState;
   4715         if (ims != null && ims.mBatchEditNesting != 0) {
   4716             ims.mBatchEditNesting = 0;
   4717             finishBatchEdit(ims);
   4718         }
   4719     }
   4720 
   4721     void finishBatchEdit(final InputMethodState ims) {
   4722         onEndBatchEdit();
   4723 
   4724         if (ims.mContentChanged || ims.mSelectionModeChanged) {
   4725             updateAfterEdit();
   4726             reportExtractedText();
   4727         } else if (ims.mCursorChanged) {
   4728             // Cheezy way to get us to report the current cursor location.
   4729             invalidateCursor();
   4730         }
   4731     }
   4732 
   4733     void updateAfterEdit() {
   4734         invalidate();
   4735         int curs = Selection.getSelectionStart(mText);
   4736 
   4737         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
   4738                              Gravity.BOTTOM) {
   4739             registerForPreDraw();
   4740         }
   4741 
   4742         if (curs >= 0) {
   4743             mHighlightPathBogus = true;
   4744 
   4745             if (isFocused()) {
   4746                 mShowCursor = SystemClock.uptimeMillis();
   4747                 makeBlink();
   4748             }
   4749         }
   4750 
   4751         checkForResize();
   4752     }
   4753 
   4754     /**
   4755      * Called by the framework in response to a request to begin a batch
   4756      * of edit operations through a call to link {@link #beginBatchEdit()}.
   4757      */
   4758     public void onBeginBatchEdit() {
   4759     }
   4760 
   4761     /**
   4762      * Called by the framework in response to a request to end a batch
   4763      * of edit operations through a call to link {@link #endBatchEdit}.
   4764      */
   4765     public void onEndBatchEdit() {
   4766     }
   4767 
   4768     /**
   4769      * Called by the framework in response to a private command from the
   4770      * current method, provided by it calling
   4771      * {@link InputConnection#performPrivateCommand
   4772      * InputConnection.performPrivateCommand()}.
   4773      *
   4774      * @param action The action name of the command.
   4775      * @param data Any additional data for the command.  This may be null.
   4776      * @return Return true if you handled the command, else false.
   4777      */
   4778     public boolean onPrivateIMECommand(String action, Bundle data) {
   4779         return false;
   4780     }
   4781 
   4782     private void nullLayouts() {
   4783         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
   4784             mSavedLayout = (BoringLayout) mLayout;
   4785         }
   4786         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
   4787             mSavedHintLayout = (BoringLayout) mHintLayout;
   4788         }
   4789 
   4790         mLayout = mHintLayout = null;
   4791     }
   4792 
   4793     /**
   4794      * Make a new Layout based on the already-measured size of the view,
   4795      * on the assumption that it was measured correctly at some point.
   4796      */
   4797     private void assumeLayout() {
   4798         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   4799 
   4800         if (width < 1) {
   4801             width = 0;
   4802         }
   4803 
   4804         int physicalWidth = width;
   4805 
   4806         if (mHorizontallyScrolling) {
   4807             width = VERY_WIDE;
   4808         }
   4809 
   4810         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
   4811                       physicalWidth, false);
   4812     }
   4813 
   4814     /**
   4815      * The width passed in is now the desired layout width,
   4816      * not the full view width with padding.
   4817      * {@hide}
   4818      */
   4819     protected void makeNewLayout(int w, int hintWidth,
   4820                                  BoringLayout.Metrics boring,
   4821                                  BoringLayout.Metrics hintBoring,
   4822                                  int ellipsisWidth, boolean bringIntoView) {
   4823         stopMarquee();
   4824 
   4825         mHighlightPathBogus = true;
   4826 
   4827         if (w < 0) {
   4828             w = 0;
   4829         }
   4830         if (hintWidth < 0) {
   4831             hintWidth = 0;
   4832         }
   4833 
   4834         Layout.Alignment alignment;
   4835         switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   4836             case Gravity.CENTER_HORIZONTAL:
   4837                 alignment = Layout.Alignment.ALIGN_CENTER;
   4838                 break;
   4839 
   4840             case Gravity.RIGHT:
   4841                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
   4842                 break;
   4843 
   4844             default:
   4845                 alignment = Layout.Alignment.ALIGN_NORMAL;
   4846         }
   4847 
   4848         boolean shouldEllipsize = mEllipsize != null && mInput == null;
   4849 
   4850         if (mText instanceof Spannable) {
   4851             mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
   4852                     alignment, mSpacingMult,
   4853                     mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
   4854                     ellipsisWidth);
   4855         } else {
   4856             if (boring == UNKNOWN_BORING) {
   4857                 boring = BoringLayout.isBoring(mTransformed, mTextPaint,
   4858                                                mBoring);
   4859                 if (boring != null) {
   4860                     mBoring = boring;
   4861                 }
   4862             }
   4863 
   4864             if (boring != null) {
   4865                 if (boring.width <= w &&
   4866                     (mEllipsize == null || boring.width <= ellipsisWidth)) {
   4867                     if (mSavedLayout != null) {
   4868                         mLayout = mSavedLayout.
   4869                                 replaceOrMake(mTransformed, mTextPaint,
   4870                                 w, alignment, mSpacingMult, mSpacingAdd,
   4871                                 boring, mIncludePad);
   4872                     } else {
   4873                         mLayout = BoringLayout.make(mTransformed, mTextPaint,
   4874                                 w, alignment, mSpacingMult, mSpacingAdd,
   4875                                 boring, mIncludePad);
   4876                     }
   4877                     // Log.e("aaa", "Boring: " + mTransformed);
   4878 
   4879                     mSavedLayout = (BoringLayout) mLayout;
   4880                 } else if (shouldEllipsize && boring.width <= w) {
   4881                     if (mSavedLayout != null) {
   4882                         mLayout = mSavedLayout.
   4883                                 replaceOrMake(mTransformed, mTextPaint,
   4884                                 w, alignment, mSpacingMult, mSpacingAdd,
   4885                                 boring, mIncludePad, mEllipsize,
   4886                                 ellipsisWidth);
   4887                     } else {
   4888                         mLayout = BoringLayout.make(mTransformed, mTextPaint,
   4889                                 w, alignment, mSpacingMult, mSpacingAdd,
   4890                                 boring, mIncludePad, mEllipsize,
   4891                                 ellipsisWidth);
   4892                     }
   4893                 } else if (shouldEllipsize) {
   4894                     mLayout = new StaticLayout(mTransformed,
   4895                                 0, mTransformed.length(),
   4896                                 mTextPaint, w, alignment, mSpacingMult,
   4897                                 mSpacingAdd, mIncludePad, mEllipsize,
   4898                                 ellipsisWidth);
   4899                 } else {
   4900                     mLayout = new StaticLayout(mTransformed, mTextPaint,
   4901                             w, alignment, mSpacingMult, mSpacingAdd,
   4902                             mIncludePad);
   4903                     // Log.e("aaa", "Boring but wide: " + mTransformed);
   4904                 }
   4905             } else if (shouldEllipsize) {
   4906                 mLayout = new StaticLayout(mTransformed,
   4907                             0, mTransformed.length(),
   4908                             mTextPaint, w, alignment, mSpacingMult,
   4909                             mSpacingAdd, mIncludePad, mEllipsize,
   4910                             ellipsisWidth);
   4911             } else {
   4912                 mLayout = new StaticLayout(mTransformed, mTextPaint,
   4913                         w, alignment, mSpacingMult, mSpacingAdd,
   4914                         mIncludePad);
   4915             }
   4916         }
   4917 
   4918         shouldEllipsize = mEllipsize != null;
   4919         mHintLayout = null;
   4920 
   4921         if (mHint != null) {
   4922             if (shouldEllipsize) hintWidth = w;
   4923 
   4924             if (hintBoring == UNKNOWN_BORING) {
   4925                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
   4926                                                    mHintBoring);
   4927                 if (hintBoring != null) {
   4928                     mHintBoring = hintBoring;
   4929                 }
   4930             }
   4931 
   4932             if (hintBoring != null) {
   4933                 if (hintBoring.width <= hintWidth &&
   4934                     (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
   4935                     if (mSavedHintLayout != null) {
   4936                         mHintLayout = mSavedHintLayout.
   4937                                 replaceOrMake(mHint, mTextPaint,
   4938                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4939                                 hintBoring, mIncludePad);
   4940                     } else {
   4941                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   4942                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4943                                 hintBoring, mIncludePad);
   4944                     }
   4945 
   4946                     mSavedHintLayout = (BoringLayout) mHintLayout;
   4947                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
   4948                     if (mSavedHintLayout != null) {
   4949                         mHintLayout = mSavedHintLayout.
   4950                                 replaceOrMake(mHint, mTextPaint,
   4951                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4952                                 hintBoring, mIncludePad, mEllipsize,
   4953                                 ellipsisWidth);
   4954                     } else {
   4955                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   4956                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4957                                 hintBoring, mIncludePad, mEllipsize,
   4958                                 ellipsisWidth);
   4959                     }
   4960                 } else if (shouldEllipsize) {
   4961                     mHintLayout = new StaticLayout(mHint,
   4962                                 0, mHint.length(),
   4963                                 mTextPaint, hintWidth, alignment, mSpacingMult,
   4964                                 mSpacingAdd, mIncludePad, mEllipsize,
   4965                                 ellipsisWidth);
   4966                 } else {
   4967                     mHintLayout = new StaticLayout(mHint, mTextPaint,
   4968                             hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4969                             mIncludePad);
   4970                 }
   4971             } else if (shouldEllipsize) {
   4972                 mHintLayout = new StaticLayout(mHint,
   4973                             0, mHint.length(),
   4974                             mTextPaint, hintWidth, alignment, mSpacingMult,
   4975                             mSpacingAdd, mIncludePad, mEllipsize,
   4976                             ellipsisWidth);
   4977             } else {
   4978                 mHintLayout = new StaticLayout(mHint, mTextPaint,
   4979                         hintWidth, alignment, mSpacingMult, mSpacingAdd,
   4980                         mIncludePad);
   4981             }
   4982         }
   4983 
   4984         if (bringIntoView) {
   4985             registerForPreDraw();
   4986         }
   4987 
   4988         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   4989             if (!compressText(ellipsisWidth)) {
   4990                 final int height = mLayoutParams.height;
   4991                 // If the size of the view does not depend on the size of the text, try to
   4992                 // start the marquee immediately
   4993                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
   4994                     startMarquee();
   4995                 } else {
   4996                     // Defer the start of the marquee until we know our width (see setFrame())
   4997                     mRestartMarquee = true;
   4998                 }
   4999             }
   5000         }
   5001     }
   5002 
   5003     private boolean compressText(float width) {
   5004         // Only compress the text if it hasn't been compressed by the previous pass
   5005         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
   5006                 mTextPaint.getTextScaleX() == 1.0f) {
   5007             final float textWidth = mLayout.getLineWidth(0);
   5008             final float overflow = (textWidth + 1.0f - width) / width;
   5009             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
   5010                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
   5011                 post(new Runnable() {
   5012                     public void run() {
   5013                         requestLayout();
   5014                     }
   5015                 });
   5016                 return true;
   5017             }
   5018         }
   5019 
   5020         return false;
   5021     }
   5022 
   5023     private static int desired(Layout layout) {
   5024         int n = layout.getLineCount();
   5025         CharSequence text = layout.getText();
   5026         float max = 0;
   5027 
   5028         // if any line was wrapped, we can't use it.
   5029         // but it's ok for the last line not to have a newline
   5030 
   5031         for (int i = 0; i < n - 1; i++) {
   5032             if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
   5033                 return -1;
   5034         }
   5035 
   5036         for (int i = 0; i < n; i++) {
   5037             max = Math.max(max, layout.getLineWidth(i));
   5038         }
   5039 
   5040         return (int) FloatMath.ceil(max);
   5041     }
   5042 
   5043     /**
   5044      * Set whether the TextView includes extra top and bottom padding to make
   5045      * room for accents that go above the normal ascent and descent.
   5046      * The default is true.
   5047      *
   5048      * @attr ref android.R.styleable#TextView_includeFontPadding
   5049      */
   5050     public void setIncludeFontPadding(boolean includepad) {
   5051         mIncludePad = includepad;
   5052 
   5053         if (mLayout != null) {
   5054             nullLayouts();
   5055             requestLayout();
   5056             invalidate();
   5057         }
   5058     }
   5059 
   5060     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
   5061 
   5062     @Override
   5063     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   5064         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   5065         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   5066         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   5067         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   5068 
   5069         int width;
   5070         int height;
   5071 
   5072         BoringLayout.Metrics boring = UNKNOWN_BORING;
   5073         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
   5074 
   5075         int des = -1;
   5076         boolean fromexisting = false;
   5077 
   5078         if (widthMode == MeasureSpec.EXACTLY) {
   5079             // Parent has told us how big to be. So be it.
   5080             width = widthSize;
   5081         } else {
   5082             if (mLayout != null && mEllipsize == null) {
   5083                 des = desired(mLayout);
   5084             }
   5085 
   5086             if (des < 0) {
   5087                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
   5088                 if (boring != null) {
   5089                     mBoring = boring;
   5090                 }
   5091             } else {
   5092                 fromexisting = true;
   5093             }
   5094 
   5095             if (boring == null || boring == UNKNOWN_BORING) {
   5096                 if (des < 0) {
   5097                     des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
   5098                 }
   5099 
   5100                 width = des;
   5101             } else {
   5102                 width = boring.width;
   5103             }
   5104 
   5105             final Drawables dr = mDrawables;
   5106             if (dr != null) {
   5107                 width = Math.max(width, dr.mDrawableWidthTop);
   5108                 width = Math.max(width, dr.mDrawableWidthBottom);
   5109             }
   5110 
   5111             if (mHint != null) {
   5112                 int hintDes = -1;
   5113                 int hintWidth;
   5114 
   5115                 if (mHintLayout != null && mEllipsize == null) {
   5116                     hintDes = desired(mHintLayout);
   5117                 }
   5118 
   5119                 if (hintDes < 0) {
   5120                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
   5121                     if (hintBoring != null) {
   5122                         mHintBoring = hintBoring;
   5123                     }
   5124                 }
   5125 
   5126                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
   5127                     if (hintDes < 0) {
   5128                         hintDes = (int) FloatMath.ceil(
   5129                                 Layout.getDesiredWidth(mHint, mTextPaint));
   5130                     }
   5131 
   5132                     hintWidth = hintDes;
   5133                 } else {
   5134                     hintWidth = hintBoring.width;
   5135                 }
   5136 
   5137                 if (hintWidth > width) {
   5138                     width = hintWidth;
   5139                 }
   5140             }
   5141 
   5142             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
   5143 
   5144             if (mMaxWidthMode == EMS) {
   5145                 width = Math.min(width, mMaxWidth * getLineHeight());
   5146             } else {
   5147                 width = Math.min(width, mMaxWidth);
   5148             }
   5149 
   5150             if (mMinWidthMode == EMS) {
   5151                 width = Math.max(width, mMinWidth * getLineHeight());
   5152             } else {
   5153                 width = Math.max(width, mMinWidth);
   5154             }
   5155 
   5156             // Check against our minimum width
   5157             width = Math.max(width, getSuggestedMinimumWidth());
   5158 
   5159             if (widthMode == MeasureSpec.AT_MOST) {
   5160                 width = Math.min(widthSize, width);
   5161             }
   5162         }
   5163 
   5164         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
   5165         int unpaddedWidth = want;
   5166         int hintWant = want;
   5167 
   5168         if (mHorizontallyScrolling)
   5169             want = VERY_WIDE;
   5170 
   5171         int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
   5172 
   5173         if (mLayout == null) {
   5174             makeNewLayout(want, hintWant, boring, hintBoring,
   5175                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   5176         } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
   5177                    (mLayout.getEllipsizedWidth() !=
   5178                         width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
   5179             if (mHint == null && mEllipsize == null &&
   5180                     want > mLayout.getWidth() &&
   5181                     (mLayout instanceof BoringLayout ||
   5182                             (fromexisting && des >= 0 && des <= want))) {
   5183                 mLayout.increaseWidthTo(want);
   5184             } else {
   5185                 makeNewLayout(want, hintWant, boring, hintBoring,
   5186                               width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   5187             }
   5188         } else {
   5189             // Width has not changed.
   5190         }
   5191 
   5192         if (heightMode == MeasureSpec.EXACTLY) {
   5193             // Parent has told us how big to be. So be it.
   5194             height = heightSize;
   5195             mDesiredHeightAtMeasure = -1;
   5196         } else {
   5197             int desired = getDesiredHeight();
   5198 
   5199             height = desired;
   5200             mDesiredHeightAtMeasure = desired;
   5201 
   5202             if (heightMode == MeasureSpec.AT_MOST) {
   5203                 height = Math.min(desired, heightSize);
   5204             }
   5205         }
   5206 
   5207         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
   5208         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
   5209             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
   5210         }
   5211 
   5212         /*
   5213          * We didn't let makeNewLayout() register to bring the cursor into view,
   5214          * so do it here if there is any possibility that it is needed.
   5215          */
   5216         if (mMovement != null ||
   5217             mLayout.getWidth() > unpaddedWidth ||
   5218             mLayout.getHeight() > unpaddedHeight) {
   5219             registerForPreDraw();
   5220         } else {
   5221             scrollTo(0, 0);
   5222         }
   5223 
   5224         setMeasuredDimension(width, height);
   5225     }
   5226 
   5227     private int getDesiredHeight() {
   5228         return Math.max(
   5229                 getDesiredHeight(mLayout, true),
   5230                 getDesiredHeight(mHintLayout, mEllipsize != null));
   5231     }
   5232 
   5233     private int getDesiredHeight(Layout layout, boolean cap) {
   5234         if (layout == null) {
   5235             return 0;
   5236         }
   5237 
   5238         int linecount = layout.getLineCount();
   5239         int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
   5240         int desired = layout.getLineTop(linecount);
   5241 
   5242         final Drawables dr = mDrawables;
   5243         if (dr != null) {
   5244             desired = Math.max(desired, dr.mDrawableHeightLeft);
   5245             desired = Math.max(desired, dr.mDrawableHeightRight);
   5246         }
   5247 
   5248         desired += pad;
   5249 
   5250         if (mMaxMode == LINES) {
   5251             /*
   5252              * Don't cap the hint to a certain number of lines.
   5253              * (Do cap it, though, if we have a maximum pixel height.)
   5254              */
   5255             if (cap) {
   5256                 if (linecount > mMaximum) {
   5257                     desired = layout.getLineTop(mMaximum) +
   5258                               layout.getBottomPadding();
   5259 
   5260                     if (dr != null) {
   5261                         desired = Math.max(desired, dr.mDrawableHeightLeft);
   5262                         desired = Math.max(desired, dr.mDrawableHeightRight);
   5263                     }
   5264 
   5265                     desired += pad;
   5266                     linecount = mMaximum;
   5267                 }
   5268             }
   5269         } else {
   5270             desired = Math.min(desired, mMaximum);
   5271         }
   5272 
   5273         if (mMinMode == LINES) {
   5274             if (linecount < mMinimum) {
   5275                 desired += getLineHeight() * (mMinimum - linecount);
   5276             }
   5277         } else {
   5278             desired = Math.max(desired, mMinimum);
   5279         }
   5280 
   5281         // Check against our minimum height
   5282         desired = Math.max(desired, getSuggestedMinimumHeight());
   5283 
   5284         return desired;
   5285     }
   5286 
   5287     /**
   5288      * Check whether a change to the existing text layout requires a
   5289      * new view layout.
   5290      */
   5291     private void checkForResize() {
   5292         boolean sizeChanged = false;
   5293 
   5294         if (mLayout != null) {
   5295             // Check if our width changed
   5296             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
   5297                 sizeChanged = true;
   5298                 invalidate();
   5299             }
   5300 
   5301             // Check if our height changed
   5302             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
   5303                 int desiredHeight = getDesiredHeight();
   5304 
   5305                 if (desiredHeight != this.getHeight()) {
   5306                     sizeChanged = true;
   5307                 }
   5308             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
   5309                 if (mDesiredHeightAtMeasure >= 0) {
   5310                     int desiredHeight = getDesiredHeight();
   5311 
   5312                     if (desiredHeight != mDesiredHeightAtMeasure) {
   5313                         sizeChanged = true;
   5314                     }
   5315                 }
   5316             }
   5317         }
   5318 
   5319         if (sizeChanged) {
   5320             requestLayout();
   5321             // caller will have already invalidated
   5322         }
   5323     }
   5324 
   5325     /**
   5326      * Check whether entirely new text requires a new view layout
   5327      * or merely a new text layout.
   5328      */
   5329     private void checkForRelayout() {
   5330         // If we have a fixed width, we can just swap in a new text layout
   5331         // if the text height stays the same or if the view height is fixed.
   5332 
   5333         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
   5334                 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
   5335                 (mHint == null || mHintLayout != null) &&
   5336                 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
   5337             // Static width, so try making a new text layout.
   5338 
   5339             int oldht = mLayout.getHeight();
   5340             int want = mLayout.getWidth();
   5341             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   5342 
   5343             /*
   5344              * No need to bring the text into view, since the size is not
   5345              * changing (unless we do the requestLayout(), in which case it
   5346              * will happen at measure).
   5347              */
   5348             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   5349                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
   5350                           false);
   5351 
   5352             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
   5353                 // In a fixed-height view, so use our new text layout.
   5354                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
   5355                     mLayoutParams.height != LayoutParams.MATCH_PARENT) {
   5356                     invalidate();
   5357                     return;
   5358                 }
   5359 
   5360                 // Dynamic height, but height has stayed the same,
   5361                 // so use our new text layout.
   5362                 if (mLayout.getHeight() == oldht &&
   5363                     (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
   5364                     invalidate();
   5365                     return;
   5366                 }
   5367             }
   5368 
   5369             // We lose: the height has changed and we have a dynamic height.
   5370             // Request a new view layout using our new text layout.
   5371             requestLayout();
   5372             invalidate();
   5373         } else {
   5374             // Dynamic width, so we have no choice but to request a new
   5375             // view layout with a new text layout.
   5376 
   5377             nullLayouts();
   5378             requestLayout();
   5379             invalidate();
   5380         }
   5381     }
   5382 
   5383     /**
   5384      * Returns true if anything changed.
   5385      */
   5386     private boolean bringTextIntoView() {
   5387         int line = 0;
   5388         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   5389             line = mLayout.getLineCount() - 1;
   5390         }
   5391 
   5392         Layout.Alignment a = mLayout.getParagraphAlignment(line);
   5393         int dir = mLayout.getParagraphDirection(line);
   5394         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   5395         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   5396         int ht = mLayout.getHeight();
   5397 
   5398         int scrollx, scrolly;
   5399 
   5400         if (a == Layout.Alignment.ALIGN_CENTER) {
   5401             /*
   5402              * Keep centered if possible, or, if it is too wide to fit,
   5403              * keep leading edge in view.
   5404              */
   5405 
   5406             int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
   5407             int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
   5408 
   5409             if (right - left < hspace) {
   5410                 scrollx = (right + left) / 2 - hspace / 2;
   5411             } else {
   5412                 if (dir < 0) {
   5413                     scrollx = right - hspace;
   5414                 } else {
   5415                     scrollx = left;
   5416                 }
   5417             }
   5418         } else if (a == Layout.Alignment.ALIGN_NORMAL) {
   5419             /*
   5420              * Keep leading edge in view.
   5421              */
   5422 
   5423             if (dir < 0) {
   5424                 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
   5425                 scrollx = right - hspace;
   5426             } else {
   5427                 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
   5428             }
   5429         } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
   5430             /*
   5431              * Keep trailing edge in view.
   5432              */
   5433 
   5434             if (dir < 0) {
   5435                 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
   5436             } else {
   5437                 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
   5438                 scrollx = right - hspace;
   5439             }
   5440         }
   5441 
   5442         if (ht < vspace) {
   5443             scrolly = 0;
   5444         } else {
   5445             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   5446                 scrolly = ht - vspace;
   5447             } else {
   5448                 scrolly = 0;
   5449             }
   5450         }
   5451 
   5452         if (scrollx != mScrollX || scrolly != mScrollY) {
   5453             scrollTo(scrollx, scrolly);
   5454             return true;
   5455         } else {
   5456             return false;
   5457         }
   5458     }
   5459 
   5460     /**
   5461      * Move the point, specified by the offset, into the view if it is needed.
   5462      * This has to be called after layout. Returns true if anything changed.
   5463      */
   5464     public boolean bringPointIntoView(int offset) {
   5465         boolean changed = false;
   5466 
   5467         int line = mLayout.getLineForOffset(offset);
   5468 
   5469         // FIXME: Is it okay to truncate this, or should we round?
   5470         final int x = (int)mLayout.getPrimaryHorizontal(offset);
   5471         final int top = mLayout.getLineTop(line);
   5472         final int bottom = mLayout.getLineTop(line+1);
   5473 
   5474         int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
   5475         int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
   5476         int ht = mLayout.getHeight();
   5477 
   5478         int grav;
   5479 
   5480         switch (mLayout.getParagraphAlignment(line)) {
   5481             case ALIGN_NORMAL:
   5482                 grav = 1;
   5483                 break;
   5484 
   5485             case ALIGN_OPPOSITE:
   5486                 grav = -1;
   5487                 break;
   5488 
   5489             default:
   5490                 grav = 0;
   5491         }
   5492 
   5493         grav *= mLayout.getParagraphDirection(line);
   5494 
   5495         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   5496         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   5497 
   5498         int hslack = (bottom - top) / 2;
   5499         int vslack = hslack;
   5500 
   5501         if (vslack > vspace / 4)
   5502             vslack = vspace / 4;
   5503         if (hslack > hspace / 4)
   5504             hslack = hspace / 4;
   5505 
   5506         int hs = mScrollX;
   5507         int vs = mScrollY;
   5508 
   5509         if (top - vs < vslack)
   5510             vs = top - vslack;
   5511         if (bottom - vs > vspace - vslack)
   5512             vs = bottom - (vspace - vslack);
   5513         if (ht - vs < vspace)
   5514             vs = ht - vspace;
   5515         if (0 - vs > 0)
   5516             vs = 0;
   5517 
   5518         if (grav != 0) {
   5519             if (x - hs < hslack) {
   5520                 hs = x - hslack;
   5521             }
   5522             if (x - hs > hspace - hslack) {
   5523                 hs = x - (hspace - hslack);
   5524             }
   5525         }
   5526 
   5527         if (grav < 0) {
   5528             if (left - hs > 0)
   5529                 hs = left;
   5530             if (right - hs < hspace)
   5531                 hs = right - hspace;
   5532         } else if (grav > 0) {
   5533             if (right - hs < hspace)
   5534                 hs = right - hspace;
   5535             if (left - hs > 0)
   5536                 hs = left;
   5537         } else /* grav == 0 */ {
   5538             if (right - left <= hspace) {
   5539                 /*
   5540                  * If the entire text fits, center it exactly.
   5541                  */
   5542                 hs = left - (hspace - (right - left)) / 2;
   5543             } else if (x > right - hslack) {
   5544                 /*
   5545                  * If we are near the right edge, keep the right edge
   5546                  * at the edge of the view.
   5547                  */
   5548                 hs = right - hspace;
   5549             } else if (x < left + hslack) {
   5550                 /*
   5551                  * If we are near the left edge, keep the left edge
   5552                  * at the edge of the view.
   5553                  */
   5554                 hs = left;
   5555             } else if (left > hs) {
   5556                 /*
   5557                  * Is there whitespace visible at the left?  Fix it if so.
   5558                  */
   5559                 hs = left;
   5560             } else if (right < hs + hspace) {
   5561                 /*
   5562                  * Is there whitespace visible at the right?  Fix it if so.
   5563                  */
   5564                 hs = right - hspace;
   5565             } else {
   5566                 /*
   5567                  * Otherwise, float as needed.
   5568                  */
   5569                 if (x - hs < hslack) {
   5570                     hs = x - hslack;
   5571                 }
   5572                 if (x - hs > hspace - hslack) {
   5573                     hs = x - (hspace - hslack);
   5574                 }
   5575             }
   5576         }
   5577 
   5578         if (hs != mScrollX || vs != mScrollY) {
   5579             if (mScroller == null) {
   5580                 scrollTo(hs, vs);
   5581             } else {
   5582                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
   5583                 int dx = hs - mScrollX;
   5584                 int dy = vs - mScrollY;
   5585 
   5586                 if (duration > ANIMATED_SCROLL_GAP) {
   5587                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
   5588                     awakenScrollBars(mScroller.getDuration());
   5589                     invalidate();
   5590                 } else {
   5591                     if (!mScroller.isFinished()) {
   5592                         mScroller.abortAnimation();
   5593                     }
   5594 
   5595                     scrollBy(dx, dy);
   5596                 }
   5597 
   5598                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
   5599             }
   5600 
   5601             changed = true;
   5602         }
   5603 
   5604         if (isFocused()) {
   5605             // This offsets because getInterestingRect() is in terms of
   5606             // viewport coordinates, but requestRectangleOnScreen()
   5607             // is in terms of content coordinates.
   5608 
   5609             Rect r = new Rect();
   5610             getInterestingRect(r, x, top, bottom, line);
   5611             r.offset(mScrollX, mScrollY);
   5612 
   5613             if (requestRectangleOnScreen(r)) {
   5614                 changed = true;
   5615             }
   5616         }
   5617 
   5618         return changed;
   5619     }
   5620 
   5621     /**
   5622      * Move the cursor, if needed, so that it is at an offset that is visible
   5623      * to the user.  This will not move the cursor if it represents more than
   5624      * one character (a selection range).  This will only work if the
   5625      * TextView contains spannable text; otherwise it will do nothing.
   5626      */
   5627     public boolean moveCursorToVisibleOffset() {
   5628         if (!(mText instanceof Spannable)) {
   5629             return false;
   5630         }
   5631         int start = Selection.getSelectionStart(mText);
   5632         int end = Selection.getSelectionEnd(mText);
   5633         if (start != end) {
   5634             return false;
   5635         }
   5636 
   5637         // First: make sure the line is visible on screen:
   5638 
   5639         int line = mLayout.getLineForOffset(start);
   5640 
   5641         final int top = mLayout.getLineTop(line);
   5642         final int bottom = mLayout.getLineTop(line+1);
   5643         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   5644         int vslack = (bottom - top) / 2;
   5645         if (vslack > vspace / 4)
   5646             vslack = vspace / 4;
   5647         final int vs = mScrollY;
   5648 
   5649         if (top < (vs+vslack)) {
   5650             line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
   5651         } else if (bottom > (vspace+vs-vslack)) {
   5652             line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
   5653         }
   5654 
   5655         // Next: make sure the character is visible on screen:
   5656 
   5657         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   5658         final int hs = mScrollX;
   5659         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
   5660         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
   5661 
   5662         int newStart = start;
   5663         if (newStart < leftChar) {
   5664             newStart = leftChar;
   5665         } else if (newStart > rightChar) {
   5666             newStart = rightChar;
   5667         }
   5668 
   5669         if (newStart != start) {
   5670             Selection.setSelection((Spannable)mText, newStart);
   5671             return true;
   5672         }
   5673 
   5674         return false;
   5675     }
   5676 
   5677     @Override
   5678     public void computeScroll() {
   5679         if (mScroller != null) {
   5680             if (mScroller.computeScrollOffset()) {
   5681                 mScrollX = mScroller.getCurrX();
   5682                 mScrollY = mScroller.getCurrY();
   5683                 postInvalidate();  // So we draw again
   5684             }
   5685         }
   5686     }
   5687 
   5688     private void getInterestingRect(Rect r, int h, int top, int bottom,
   5689                                     int line) {
   5690         int paddingTop = getExtendedPaddingTop();
   5691         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   5692             paddingTop += getVerticalOffset(false);
   5693         }
   5694         top += paddingTop;
   5695         bottom += paddingTop;
   5696         h += getCompoundPaddingLeft();
   5697 
   5698         if (line == 0)
   5699             top -= getExtendedPaddingTop();
   5700         if (line == mLayout.getLineCount() - 1)
   5701             bottom += getExtendedPaddingBottom();
   5702 
   5703         r.set(h, top, h+1, bottom);
   5704         r.offset(-mScrollX, -mScrollY);
   5705     }
   5706 
   5707     @Override
   5708     public void debug(int depth) {
   5709         super.debug(depth);
   5710 
   5711         String output = debugIndent(depth);
   5712         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
   5713                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
   5714                 + "} ";
   5715 
   5716         if (mText != null) {
   5717 
   5718             output += "mText=\"" + mText + "\" ";
   5719             if (mLayout != null) {
   5720                 output += "mLayout width=" + mLayout.getWidth()
   5721                         + " height=" + mLayout.getHeight();
   5722             }
   5723         } else {
   5724             output += "mText=NULL";
   5725         }
   5726         Log.d(VIEW_LOG_TAG, output);
   5727     }
   5728 
   5729     /**
   5730      * Convenience for {@link Selection#getSelectionStart}.
   5731      */
   5732     @ViewDebug.ExportedProperty
   5733     public int getSelectionStart() {
   5734         return Selection.getSelectionStart(getText());
   5735     }
   5736 
   5737     /**
   5738      * Convenience for {@link Selection#getSelectionEnd}.
   5739      */
   5740     @ViewDebug.ExportedProperty
   5741     public int getSelectionEnd() {
   5742         return Selection.getSelectionEnd(getText());
   5743     }
   5744 
   5745     /**
   5746      * Return true iff there is a selection inside this text view.
   5747      */
   5748     public boolean hasSelection() {
   5749         return getSelectionStart() != getSelectionEnd();
   5750     }
   5751 
   5752     /**
   5753      * Sets the properties of this field (lines, horizontally scrolling,
   5754      * transformation method) to be for a single-line input.
   5755      *
   5756      * @attr ref android.R.styleable#TextView_singleLine
   5757      */
   5758     public void setSingleLine() {
   5759         setSingleLine(true);
   5760     }
   5761 
   5762     /**
   5763      * If true, sets the properties of this field (lines, horizontally
   5764      * scrolling, transformation method) to be for a single-line input;
   5765      * if false, restores these to the default conditions.
   5766      * Note that calling this with false restores default conditions,
   5767      * not necessarily those that were in effect prior to calling
   5768      * it with true.
   5769      *
   5770      * @attr ref android.R.styleable#TextView_singleLine
   5771      */
   5772     @android.view.RemotableViewMethod
   5773     public void setSingleLine(boolean singleLine) {
   5774         if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
   5775                 == EditorInfo.TYPE_CLASS_TEXT) {
   5776             if (singleLine) {
   5777                 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   5778             } else {
   5779                 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   5780             }
   5781         }
   5782         applySingleLine(singleLine, true);
   5783     }
   5784 
   5785     private void applySingleLine(boolean singleLine, boolean applyTransformation) {
   5786         mSingleLine = singleLine;
   5787         if (singleLine) {
   5788             setLines(1);
   5789             setHorizontallyScrolling(true);
   5790             if (applyTransformation) {
   5791                 setTransformationMethod(SingleLineTransformationMethod.
   5792                                         getInstance());
   5793             }
   5794         } else {
   5795             setMaxLines(Integer.MAX_VALUE);
   5796             setHorizontallyScrolling(false);
   5797             if (applyTransformation) {
   5798                 setTransformationMethod(null);
   5799             }
   5800         }
   5801     }
   5802 
   5803     /**
   5804      * Causes words in the text that are longer than the view is wide
   5805      * to be ellipsized instead of broken in the middle.  You may also
   5806      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
   5807      * to constrain the text to a single line.  Use <code>null</code>
   5808      * to turn off ellipsizing.
   5809      *
   5810      * @attr ref android.R.styleable#TextView_ellipsize
   5811      */
   5812     public void setEllipsize(TextUtils.TruncateAt where) {
   5813         mEllipsize = where;
   5814 
   5815         if (mLayout != null) {
   5816             nullLayouts();
   5817             requestLayout();
   5818             invalidate();
   5819         }
   5820     }
   5821 
   5822     /**
   5823      * Sets how many times to repeat the marquee animation. Only applied if the
   5824      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
   5825      *
   5826      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
   5827      */
   5828     public void setMarqueeRepeatLimit(int marqueeLimit) {
   5829         mMarqueeRepeatLimit = marqueeLimit;
   5830     }
   5831 
   5832     /**
   5833      * Returns where, if anywhere, words that are longer than the view
   5834      * is wide should be ellipsized.
   5835      */
   5836     @ViewDebug.ExportedProperty
   5837     public TextUtils.TruncateAt getEllipsize() {
   5838         return mEllipsize;
   5839     }
   5840 
   5841     /**
   5842      * Set the TextView so that when it takes focus, all the text is
   5843      * selected.
   5844      *
   5845      * @attr ref android.R.styleable#TextView_selectAllOnFocus
   5846      */
   5847     @android.view.RemotableViewMethod
   5848     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
   5849         mSelectAllOnFocus = selectAllOnFocus;
   5850 
   5851         if (selectAllOnFocus && !(mText instanceof Spannable)) {
   5852             setText(mText, BufferType.SPANNABLE);
   5853         }
   5854     }
   5855 
   5856     /**
   5857      * Set whether the cursor is visible.  The default is true.
   5858      *
   5859      * @attr ref android.R.styleable#TextView_cursorVisible
   5860      */
   5861     @android.view.RemotableViewMethod
   5862     public void setCursorVisible(boolean visible) {
   5863         mCursorVisible = visible;
   5864         invalidate();
   5865 
   5866         if (visible) {
   5867             makeBlink();
   5868         } else if (mBlink != null) {
   5869             mBlink.removeCallbacks(mBlink);
   5870         }
   5871     }
   5872 
   5873     private boolean canMarquee() {
   5874         int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
   5875         return width > 0 && mLayout.getLineWidth(0) > width;
   5876     }
   5877 
   5878     private void startMarquee() {
   5879         // Do not ellipsize EditText
   5880         if (mInput != null) return;
   5881 
   5882         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
   5883             return;
   5884         }
   5885 
   5886         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
   5887                 getLineCount() == 1 && canMarquee()) {
   5888 
   5889             if (mMarquee == null) mMarquee = new Marquee(this);
   5890             mMarquee.start(mMarqueeRepeatLimit);
   5891         }
   5892     }
   5893 
   5894     private void stopMarquee() {
   5895         if (mMarquee != null && !mMarquee.isStopped()) {
   5896             mMarquee.stop();
   5897         }
   5898     }
   5899 
   5900     private void startStopMarquee(boolean start) {
   5901         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   5902             if (start) {
   5903                 startMarquee();
   5904             } else {
   5905                 stopMarquee();
   5906             }
   5907         }
   5908     }
   5909 
   5910     private static final class Marquee extends Handler {
   5911         // TODO: Add an option to configure this
   5912         private static final float MARQUEE_DELTA_MAX = 0.07f;
   5913         private static final int MARQUEE_DELAY = 1200;
   5914         private static final int MARQUEE_RESTART_DELAY = 1200;
   5915         private static final int MARQUEE_RESOLUTION = 1000 / 30;
   5916         private static final int MARQUEE_PIXELS_PER_SECOND = 30;
   5917 
   5918         private static final byte MARQUEE_STOPPED = 0x0;
   5919         private static final byte MARQUEE_STARTING = 0x1;
   5920         private static final byte MARQUEE_RUNNING = 0x2;
   5921 
   5922         private static final int MESSAGE_START = 0x1;
   5923         private static final int MESSAGE_TICK = 0x2;
   5924         private static final int MESSAGE_RESTART = 0x3;
   5925 
   5926         private final WeakReference<TextView> mView;
   5927 
   5928         private byte mStatus = MARQUEE_STOPPED;
   5929         private float mScrollUnit;
   5930         private float mMaxScroll;
   5931         float mMaxFadeScroll;
   5932         private float mGhostStart;
   5933         private float mGhostOffset;
   5934         private float mFadeStop;
   5935         private int mRepeatLimit;
   5936 
   5937         float mScroll;
   5938 
   5939         Marquee(TextView v) {
   5940             final float density = v.getContext().getResources().getDisplayMetrics().density;
   5941             mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
   5942             mView = new WeakReference<TextView>(v);
   5943         }
   5944 
   5945         @Override
   5946         public void handleMessage(Message msg) {
   5947             switch (msg.what) {
   5948                 case MESSAGE_START:
   5949                     mStatus = MARQUEE_RUNNING;
   5950                     tick();
   5951                     break;
   5952                 case MESSAGE_TICK:
   5953                     tick();
   5954                     break;
   5955                 case MESSAGE_RESTART:
   5956                     if (mStatus == MARQUEE_RUNNING) {
   5957                         if (mRepeatLimit >= 0) {
   5958                             mRepeatLimit--;
   5959                         }
   5960                         start(mRepeatLimit);
   5961                     }
   5962                     break;
   5963             }
   5964         }
   5965 
   5966         void tick() {
   5967             if (mStatus != MARQUEE_RUNNING) {
   5968                 return;
   5969             }
   5970 
   5971             removeMessages(MESSAGE_TICK);
   5972 
   5973             final TextView textView = mView.get();
   5974             if (textView != null && (textView.isFocused() || textView.isSelected())) {
   5975                 mScroll += mScrollUnit;
   5976                 if (mScroll > mMaxScroll) {
   5977                     mScroll = mMaxScroll;
   5978                     sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
   5979                 } else {
   5980                     sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
   5981                 }
   5982                 textView.invalidate();
   5983             }
   5984         }
   5985 
   5986         void stop() {
   5987             mStatus = MARQUEE_STOPPED;
   5988             removeMessages(MESSAGE_START);
   5989             removeMessages(MESSAGE_RESTART);
   5990             removeMessages(MESSAGE_TICK);
   5991             resetScroll();
   5992         }
   5993 
   5994         private void resetScroll() {
   5995             mScroll = 0.0f;
   5996             final TextView textView = mView.get();
   5997             if (textView != null) textView.invalidate();
   5998         }
   5999 
   6000         void start(int repeatLimit) {
   6001             if (repeatLimit == 0) {
   6002                 stop();
   6003                 return;
   6004             }
   6005             mRepeatLimit = repeatLimit;
   6006             final TextView textView = mView.get();
   6007             if (textView != null && textView.mLayout != null) {
   6008                 mStatus = MARQUEE_STARTING;
   6009                 mScroll = 0.0f;
   6010                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
   6011                         textView.getCompoundPaddingRight();
   6012                 final float lineWidth = textView.mLayout.getLineWidth(0);
   6013                 final float gap = textWidth / 3.0f;
   6014                 mGhostStart = lineWidth - textWidth + gap;
   6015                 mMaxScroll = mGhostStart + textWidth;
   6016                 mGhostOffset = lineWidth + gap;
   6017                 mFadeStop = lineWidth + textWidth / 6.0f;
   6018                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
   6019 
   6020                 textView.invalidate();
   6021                 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
   6022             }
   6023         }
   6024 
   6025         float getGhostOffset() {
   6026             return mGhostOffset;
   6027         }
   6028 
   6029         boolean shouldDrawLeftFade() {
   6030             return mScroll <= mFadeStop;
   6031         }
   6032 
   6033         boolean shouldDrawGhost() {
   6034             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
   6035         }
   6036 
   6037         boolean isRunning() {
   6038             return mStatus == MARQUEE_RUNNING;
   6039         }
   6040 
   6041         boolean isStopped() {
   6042             return mStatus == MARQUEE_STOPPED;
   6043         }
   6044     }
   6045 
   6046     /**
   6047      * This method is called when the text is changed, in case any
   6048      * subclasses would like to know.
   6049      *
   6050      * @param text The text the TextView is displaying.
   6051      * @param start The offset of the start of the range of the text
   6052      *              that was modified.
   6053      * @param before The offset of the former end of the range of the
   6054      *               text that was modified.  If text was simply inserted,
   6055      *               this will be the same as <code>start</code>.
   6056      *               If text was replaced with new text or deleted, the
   6057      *               length of the old text was <code>before-start</code>.
   6058      * @param after The offset of the end of the range of the text
   6059      *              that was modified.  If text was simply deleted,
   6060      *              this will be the same as <code>start</code>.
   6061      *              If text was replaced with new text or inserted,
   6062      *              the length of the new text is <code>after-start</code>.
   6063      */
   6064     protected void onTextChanged(CharSequence text,
   6065                                  int start, int before, int after) {
   6066     }
   6067 
   6068     /**
   6069      * This method is called when the selection has changed, in case any
   6070      * subclasses would like to know.
   6071      *
   6072      * @param selStart The new selection start location.
   6073      * @param selEnd The new selection end location.
   6074      */
   6075     protected void onSelectionChanged(int selStart, int selEnd) {
   6076     }
   6077 
   6078     /**
   6079      * Adds a TextWatcher to the list of those whose methods are called
   6080      * whenever this TextView's text changes.
   6081      * <p>
   6082      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
   6083      * not called after {@link #setText} calls.  Now, doing {@link #setText}
   6084      * if there are any text changed listeners forces the buffer type to
   6085      * Editable if it would not otherwise be and does call this method.
   6086      */
   6087     public void addTextChangedListener(TextWatcher watcher) {
   6088         if (mListeners == null) {
   6089             mListeners = new ArrayList<TextWatcher>();
   6090         }
   6091 
   6092         mListeners.add(watcher);
   6093     }
   6094 
   6095     /**
   6096      * Removes the specified TextWatcher from the list of those whose
   6097      * methods are called
   6098      * whenever this TextView's text changes.
   6099      */
   6100     public void removeTextChangedListener(TextWatcher watcher) {
   6101         if (mListeners != null) {
   6102             int i = mListeners.indexOf(watcher);
   6103 
   6104             if (i >= 0) {
   6105                 mListeners.remove(i);
   6106             }
   6107         }
   6108     }
   6109 
   6110     private void sendBeforeTextChanged(CharSequence text, int start, int before,
   6111                                    int after) {
   6112         if (mListeners != null) {
   6113             final ArrayList<TextWatcher> list = mListeners;
   6114             final int count = list.size();
   6115             for (int i = 0; i < count; i++) {
   6116                 list.get(i).beforeTextChanged(text, start, before, after);
   6117             }
   6118         }
   6119     }
   6120 
   6121     /**
   6122      * Not private so it can be called from an inner class without going
   6123      * through a thunk.
   6124      */
   6125     void sendOnTextChanged(CharSequence text, int start, int before,
   6126                                    int after) {
   6127         if (mListeners != null) {
   6128             final ArrayList<TextWatcher> list = mListeners;
   6129             final int count = list.size();
   6130             for (int i = 0; i < count; i++) {
   6131                 list.get(i).onTextChanged(text, start, before, after);
   6132             }
   6133         }
   6134     }
   6135 
   6136     /**
   6137      * Not private so it can be called from an inner class without going
   6138      * through a thunk.
   6139      */
   6140     void sendAfterTextChanged(Editable text) {
   6141         if (mListeners != null) {
   6142             final ArrayList<TextWatcher> list = mListeners;
   6143             final int count = list.size();
   6144             for (int i = 0; i < count; i++) {
   6145                 list.get(i).afterTextChanged(text);
   6146             }
   6147         }
   6148     }
   6149 
   6150     /**
   6151      * Not private so it can be called from an inner class without going
   6152      * through a thunk.
   6153      */
   6154     void handleTextChanged(CharSequence buffer, int start,
   6155             int before, int after) {
   6156         final InputMethodState ims = mInputMethodState;
   6157         if (ims == null || ims.mBatchEditNesting == 0) {
   6158             updateAfterEdit();
   6159         }
   6160         if (ims != null) {
   6161             ims.mContentChanged = true;
   6162             if (ims.mChangedStart < 0) {
   6163                 ims.mChangedStart = start;
   6164                 ims.mChangedEnd = start+before;
   6165             } else {
   6166                 if (ims.mChangedStart > start) ims.mChangedStart = start;
   6167                 if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
   6168             }
   6169             ims.mChangedDelta += after-before;
   6170         }
   6171 
   6172         sendOnTextChanged(buffer, start, before, after);
   6173         onTextChanged(buffer, start, before, after);
   6174     }
   6175 
   6176     /**
   6177      * Not private so it can be called from an inner class without going
   6178      * through a thunk.
   6179      */
   6180     void spanChange(Spanned buf, Object what, int oldStart, int newStart,
   6181             int oldEnd, int newEnd) {
   6182         // XXX Make the start and end move together if this ends up
   6183         // spending too much time invalidating.
   6184 
   6185         boolean selChanged = false;
   6186         int newSelStart=-1, newSelEnd=-1;
   6187 
   6188         final InputMethodState ims = mInputMethodState;
   6189 
   6190         if (what == Selection.SELECTION_END) {
   6191             mHighlightPathBogus = true;
   6192             selChanged = true;
   6193             newSelEnd = newStart;
   6194 
   6195             if (!isFocused()) {
   6196                 mSelectionMoved = true;
   6197             }
   6198 
   6199             if (oldStart >= 0 || newStart >= 0) {
   6200                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
   6201                 registerForPreDraw();
   6202 
   6203                 if (isFocused()) {
   6204                     mShowCursor = SystemClock.uptimeMillis();
   6205                     makeBlink();
   6206                 }
   6207             }
   6208         }
   6209 
   6210         if (what == Selection.SELECTION_START) {
   6211             mHighlightPathBogus = true;
   6212             selChanged = true;
   6213             newSelStart = newStart;
   6214 
   6215             if (!isFocused()) {
   6216                 mSelectionMoved = true;
   6217             }
   6218 
   6219             if (oldStart >= 0 || newStart >= 0) {
   6220                 int end = Selection.getSelectionEnd(buf);
   6221                 invalidateCursor(end, oldStart, newStart);
   6222             }
   6223         }
   6224 
   6225         if (selChanged) {
   6226             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
   6227                 if (newSelStart < 0) {
   6228                     newSelStart = Selection.getSelectionStart(buf);
   6229                 }
   6230                 if (newSelEnd < 0) {
   6231                     newSelEnd = Selection.getSelectionEnd(buf);
   6232                 }
   6233                 onSelectionChanged(newSelStart, newSelEnd);
   6234             }
   6235         }
   6236 
   6237         if (what instanceof UpdateAppearance ||
   6238             what instanceof ParagraphStyle) {
   6239             if (ims == null || ims.mBatchEditNesting == 0) {
   6240                 invalidate();
   6241                 mHighlightPathBogus = true;
   6242                 checkForResize();
   6243             } else {
   6244                 ims.mContentChanged = true;
   6245             }
   6246         }
   6247 
   6248         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
   6249             mHighlightPathBogus = true;
   6250             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
   6251                 ims.mSelectionModeChanged = true;
   6252             }
   6253 
   6254             if (Selection.getSelectionStart(buf) >= 0) {
   6255                 if (ims == null || ims.mBatchEditNesting == 0) {
   6256                     invalidateCursor();
   6257                 } else {
   6258                     ims.mCursorChanged = true;
   6259                 }
   6260             }
   6261         }
   6262 
   6263         if (what instanceof ParcelableSpan) {
   6264             // If this is a span that can be sent to a remote process,
   6265             // the current extract editor would be interested in it.
   6266             if (ims != null && ims.mExtracting != null) {
   6267                 if (ims.mBatchEditNesting != 0) {
   6268                     if (oldStart >= 0) {
   6269                         if (ims.mChangedStart > oldStart) {
   6270                             ims.mChangedStart = oldStart;
   6271                         }
   6272                         if (ims.mChangedStart > oldEnd) {
   6273                             ims.mChangedStart = oldEnd;
   6274                         }
   6275                     }
   6276                     if (newStart >= 0) {
   6277                         if (ims.mChangedStart > newStart) {
   6278                             ims.mChangedStart = newStart;
   6279                         }
   6280                         if (ims.mChangedStart > newEnd) {
   6281                             ims.mChangedStart = newEnd;
   6282                         }
   6283                     }
   6284                 } else {
   6285                     if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
   6286                             + oldStart + "-" + oldEnd + ","
   6287                             + newStart + "-" + newEnd + what);
   6288                     ims.mContentChanged = true;
   6289                 }
   6290             }
   6291         }
   6292     }
   6293 
   6294     private class ChangeWatcher
   6295     implements TextWatcher, SpanWatcher {
   6296 
   6297         private CharSequence mBeforeText;
   6298 
   6299         public void beforeTextChanged(CharSequence buffer, int start,
   6300                                       int before, int after) {
   6301             if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
   6302                     + " before=" + before + " after=" + after + ": " + buffer);
   6303 
   6304             if (AccessibilityManager.getInstance(mContext).isEnabled()
   6305                     && !isPasswordInputType(mInputType)) {
   6306                 mBeforeText = buffer.toString();
   6307             }
   6308 
   6309             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
   6310         }
   6311 
   6312         public void onTextChanged(CharSequence buffer, int start,
   6313                                   int before, int after) {
   6314             if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
   6315                     + " before=" + before + " after=" + after + ": " + buffer);
   6316             TextView.this.handleTextChanged(buffer, start, before, after);
   6317 
   6318             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
   6319                     (isFocused() || isSelected() &&
   6320                     isShown())) {
   6321                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
   6322                 mBeforeText = null;
   6323             }
   6324         }
   6325 
   6326         public void afterTextChanged(Editable buffer) {
   6327             if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
   6328             TextView.this.sendAfterTextChanged(buffer);
   6329 
   6330             if (MetaKeyKeyListener.getMetaState(buffer,
   6331                                  MetaKeyKeyListener.META_SELECTING) != 0) {
   6332                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
   6333             }
   6334         }
   6335 
   6336         public void onSpanChanged(Spannable buf,
   6337                                   Object what, int s, int e, int st, int en) {
   6338             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
   6339                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
   6340             TextView.this.spanChange(buf, what, s, st, e, en);
   6341         }
   6342 
   6343         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
   6344             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
   6345                     + " what=" + what + ": " + buf);
   6346             TextView.this.spanChange(buf, what, -1, s, -1, e);
   6347         }
   6348 
   6349         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
   6350             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
   6351                     + " what=" + what + ": " + buf);
   6352             TextView.this.spanChange(buf, what, s, -1, e, -1);
   6353         }
   6354     }
   6355 
   6356     private void makeBlink() {
   6357         if (!mCursorVisible) {
   6358             if (mBlink != null) {
   6359                 mBlink.removeCallbacks(mBlink);
   6360             }
   6361 
   6362             return;
   6363         }
   6364 
   6365         if (mBlink == null)
   6366             mBlink = new Blink(this);
   6367 
   6368         mBlink.removeCallbacks(mBlink);
   6369         mBlink.postAtTime(mBlink, mShowCursor + BLINK);
   6370     }
   6371 
   6372     /**
   6373      * @hide
   6374      */
   6375     @Override
   6376     public void dispatchFinishTemporaryDetach() {
   6377         mDispatchTemporaryDetach = true;
   6378         super.dispatchFinishTemporaryDetach();
   6379         mDispatchTemporaryDetach = false;
   6380     }
   6381 
   6382     @Override
   6383     public void onStartTemporaryDetach() {
   6384         super.onStartTemporaryDetach();
   6385         // Only track when onStartTemporaryDetach() is called directly,
   6386         // usually because this instance is an editable field in a list
   6387         if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
   6388     }
   6389 
   6390     @Override
   6391     public void onFinishTemporaryDetach() {
   6392         super.onFinishTemporaryDetach();
   6393         // Only track when onStartTemporaryDetach() is called directly,
   6394         // usually because this instance is an editable field in a list
   6395         if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
   6396     }
   6397 
   6398     @Override
   6399     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
   6400         if (mTemporaryDetach) {
   6401             // If we are temporarily in the detach state, then do nothing.
   6402             super.onFocusChanged(focused, direction, previouslyFocusedRect);
   6403             return;
   6404         }
   6405 
   6406         mShowCursor = SystemClock.uptimeMillis();
   6407 
   6408         ensureEndedBatchEdit();
   6409 
   6410         if (focused) {
   6411             int selStart = getSelectionStart();
   6412             int selEnd = getSelectionEnd();
   6413 
   6414             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
   6415                 boolean selMoved = mSelectionMoved;
   6416 
   6417                 if (mMovement != null) {
   6418                     mMovement.onTakeFocus(this, (Spannable) mText, direction);
   6419                 }
   6420 
   6421                 if (mSelectAllOnFocus) {
   6422                     Selection.setSelection((Spannable) mText, 0, mText.length());
   6423                 }
   6424 
   6425                 if (selMoved && selStart >= 0 && selEnd >= 0) {
   6426                     /*
   6427                      * Someone intentionally set the selection, so let them
   6428                      * do whatever it is that they wanted to do instead of
   6429                      * the default on-focus behavior.  We reset the selection
   6430                      * here instead of just skipping the onTakeFocus() call
   6431                      * because some movement methods do something other than
   6432                      * just setting the selection in theirs and we still
   6433                      * need to go through that path.
   6434                      */
   6435 
   6436                     Selection.setSelection((Spannable) mText, selStart, selEnd);
   6437                 }
   6438                 mTouchFocusSelected = true;
   6439             }
   6440 
   6441             mFrozenWithFocus = false;
   6442             mSelectionMoved = false;
   6443 
   6444             if (mText instanceof Spannable) {
   6445                 Spannable sp = (Spannable) mText;
   6446                 MetaKeyKeyListener.resetMetaState(sp);
   6447             }
   6448 
   6449             makeBlink();
   6450 
   6451             if (mError != null) {
   6452                 showError();
   6453             }
   6454         } else {
   6455             if (mError != null) {
   6456                 hideError();
   6457             }
   6458             // Don't leave us in the middle of a batch edit.
   6459             onEndBatchEdit();
   6460         }
   6461 
   6462         startStopMarquee(focused);
   6463 
   6464         if (mTransformation != null) {
   6465             mTransformation.onFocusChanged(this, mText, focused, direction,
   6466                                            previouslyFocusedRect);
   6467         }
   6468 
   6469         super.onFocusChanged(focused, direction, previouslyFocusedRect);
   6470     }
   6471 
   6472     @Override
   6473     public void onWindowFocusChanged(boolean hasWindowFocus) {
   6474         super.onWindowFocusChanged(hasWindowFocus);
   6475 
   6476         if (hasWindowFocus) {
   6477             if (mBlink != null) {
   6478                 mBlink.uncancel();
   6479 
   6480                 if (isFocused()) {
   6481                     mShowCursor = SystemClock.uptimeMillis();
   6482                     makeBlink();
   6483                 }
   6484             }
   6485         } else {
   6486             if (mBlink != null) {
   6487                 mBlink.cancel();
   6488             }
   6489             // Don't leave us in the middle of a batch edit.
   6490             onEndBatchEdit();
   6491             if (mInputContentType != null) {
   6492                 mInputContentType.enterDown = false;
   6493             }
   6494         }
   6495 
   6496         startStopMarquee(hasWindowFocus);
   6497     }
   6498 
   6499     /**
   6500      * Use {@link BaseInputConnection#removeComposingSpans
   6501      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
   6502      * state from this text view.
   6503      */
   6504     public void clearComposingText() {
   6505         if (mText instanceof Spannable) {
   6506             BaseInputConnection.removeComposingSpans((Spannable)mText);
   6507         }
   6508     }
   6509 
   6510     @Override
   6511     public void setSelected(boolean selected) {
   6512         boolean wasSelected = isSelected();
   6513 
   6514         super.setSelected(selected);
   6515 
   6516         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   6517             if (selected) {
   6518                 startMarquee();
   6519             } else {
   6520                 stopMarquee();
   6521             }
   6522         }
   6523     }
   6524 
   6525     class CommitSelectionReceiver extends ResultReceiver {
   6526         int mNewStart;
   6527         int mNewEnd;
   6528 
   6529         CommitSelectionReceiver() {
   6530             super(getHandler());
   6531         }
   6532 
   6533         protected void onReceiveResult(int resultCode, Bundle resultData) {
   6534             if (resultCode != InputMethodManager.RESULT_SHOWN) {
   6535                 final int len = mText.length();
   6536                 if (mNewStart > len) {
   6537                     mNewStart = len;
   6538                 }
   6539                 if (mNewEnd > len) {
   6540                     mNewEnd = len;
   6541                 }
   6542                 Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
   6543             }
   6544         }
   6545     }
   6546 
   6547     @Override
   6548     public boolean onTouchEvent(MotionEvent event) {
   6549         final int action = event.getAction();
   6550         if (action == MotionEvent.ACTION_DOWN) {
   6551             // Reset this state; it will be re-set if super.onTouchEvent
   6552             // causes focus to move to the view.
   6553             mTouchFocusSelected = false;
   6554             mScrolled = false;
   6555         }
   6556 
   6557         final boolean superResult = super.onTouchEvent(event);
   6558 
   6559         /*
   6560          * Don't handle the release after a long press, because it will
   6561          * move the selection away from whatever the menu action was
   6562          * trying to affect.
   6563          */
   6564         if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
   6565             mEatTouchRelease = false;
   6566             return superResult;
   6567         }
   6568 
   6569         if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
   6570 
   6571             boolean handled = false;
   6572 
   6573             int oldSelStart = Selection.getSelectionStart(mText);
   6574             int oldSelEnd = Selection.getSelectionEnd(mText);
   6575 
   6576             if (mMovement != null) {
   6577                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
   6578             }
   6579 
   6580             if (mText instanceof Editable && onCheckIsTextEditor()) {
   6581                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
   6582                     InputMethodManager imm = (InputMethodManager)
   6583                             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
   6584 
   6585                     // This is going to be gross...  if tapping on the text view
   6586                     // causes the IME to be displayed, we don't want the selection
   6587                     // to change.  But the selection has already changed, and
   6588                     // we won't know right away whether the IME is getting
   6589                     // displayed, so...
   6590 
   6591                     int newSelStart = Selection.getSelectionStart(mText);
   6592                     int newSelEnd = Selection.getSelectionEnd(mText);
   6593                     CommitSelectionReceiver csr = null;
   6594                     if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
   6595                         csr = new CommitSelectionReceiver();
   6596                         csr.mNewStart = newSelStart;
   6597                         csr.mNewEnd = newSelEnd;
   6598                     }
   6599 
   6600                     if (imm.showSoftInput(this, 0, csr) && csr != null) {
   6601                         // The IME might get shown -- revert to the old
   6602                         // selection, and change to the new when we finally
   6603                         // find out of it is okay.
   6604                         Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
   6605                         handled = true;
   6606                     }
   6607                 }
   6608             }
   6609 
   6610             if (handled) {
   6611                 return true;
   6612             }
   6613         }
   6614 
   6615         return superResult;
   6616     }
   6617 
   6618     /**
   6619      * Returns true, only while processing a touch gesture, if the initial
   6620      * touch down event caused focus to move to the text view and as a result
   6621      * its selection changed.  Only valid while processing the touch gesture
   6622      * of interest.
   6623      */
   6624     public boolean didTouchFocusSelect() {
   6625         return mTouchFocusSelected;
   6626     }
   6627 
   6628     @Override
   6629     public void cancelLongPress() {
   6630         super.cancelLongPress();
   6631         mScrolled = true;
   6632     }
   6633 
   6634     @Override
   6635     public boolean onTrackballEvent(MotionEvent event) {
   6636         if (mMovement != null && mText instanceof Spannable &&
   6637             mLayout != null) {
   6638             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
   6639                 return true;
   6640             }
   6641         }
   6642 
   6643         return super.onTrackballEvent(event);
   6644     }
   6645 
   6646     public void setScroller(Scroller s) {
   6647         mScroller = s;
   6648     }
   6649 
   6650     private static class Blink extends Handler implements Runnable {
   6651         private WeakReference<TextView> mView;
   6652         private boolean mCancelled;
   6653 
   6654         public Blink(TextView v) {
   6655             mView = new WeakReference<TextView>(v);
   6656         }
   6657 
   6658         public void run() {
   6659             if (mCancelled) {
   6660                 return;
   6661             }
   6662 
   6663             removeCallbacks(Blink.this);
   6664 
   6665             TextView tv = mView.get();
   6666 
   6667             if (tv != null && tv.isFocused()) {
   6668                 int st = Selection.getSelectionStart(tv.mText);
   6669                 int en = Selection.getSelectionEnd(tv.mText);
   6670 
   6671                 if (st == en && st >= 0 && en >= 0) {
   6672                     if (tv.mLayout != null) {
   6673                         tv.invalidateCursorPath();
   6674                     }
   6675 
   6676                     postAtTime(this, SystemClock.uptimeMillis() + BLINK);
   6677                 }
   6678             }
   6679         }
   6680 
   6681         void cancel() {
   6682             if (!mCancelled) {
   6683                 removeCallbacks(Blink.this);
   6684                 mCancelled = true;
   6685             }
   6686         }
   6687 
   6688         void uncancel() {
   6689             mCancelled = false;
   6690         }
   6691     }
   6692 
   6693     @Override
   6694     protected float getLeftFadingEdgeStrength() {
   6695         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   6696             if (mMarquee != null && !mMarquee.isStopped()) {
   6697                 final Marquee marquee = mMarquee;
   6698                 if (marquee.shouldDrawLeftFade()) {
   6699                     return marquee.mScroll / getHorizontalFadingEdgeLength();
   6700                 } else {
   6701                     return 0.0f;
   6702                 }
   6703             } else if (getLineCount() == 1) {
   6704                 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   6705                     case Gravity.LEFT:
   6706                         return 0.0f;
   6707                     case Gravity.RIGHT:
   6708                         return (mLayout.getLineRight(0) - (mRight - mLeft) -
   6709                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
   6710                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
   6711                     case Gravity.CENTER_HORIZONTAL:
   6712                         return 0.0f;
   6713                 }
   6714             }
   6715         }
   6716         return super.getLeftFadingEdgeStrength();
   6717     }
   6718 
   6719     @Override
   6720     protected float getRightFadingEdgeStrength() {
   6721         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   6722             if (mMarquee != null && !mMarquee.isStopped()) {
   6723                 final Marquee marquee = mMarquee;
   6724                 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
   6725             } else if (getLineCount() == 1) {
   6726                 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   6727                     case Gravity.LEFT:
   6728                         final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
   6729                                 getCompoundPaddingRight();
   6730                         final float lineWidth = mLayout.getLineWidth(0);
   6731                         return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
   6732                     case Gravity.RIGHT:
   6733                         return 0.0f;
   6734                     case Gravity.CENTER_HORIZONTAL:
   6735                         return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
   6736                                 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
   6737                                 getHorizontalFadingEdgeLength();
   6738                 }
   6739             }
   6740         }
   6741         return super.getRightFadingEdgeStrength();
   6742     }
   6743 
   6744     @Override
   6745     protected int computeHorizontalScrollRange() {
   6746         if (mLayout != null)
   6747             return mLayout.getWidth();
   6748 
   6749         return super.computeHorizontalScrollRange();
   6750     }
   6751 
   6752     @Override
   6753     protected int computeVerticalScrollRange() {
   6754         if (mLayout != null)
   6755             return mLayout.getHeight();
   6756 
   6757         return super.computeVerticalScrollRange();
   6758     }
   6759 
   6760     @Override
   6761     protected int computeVerticalScrollExtent() {
   6762         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
   6763     }
   6764 
   6765     public enum BufferType {
   6766         NORMAL, SPANNABLE, EDITABLE,
   6767     }
   6768 
   6769     /**
   6770      * Returns the TextView_textColor attribute from the
   6771      * Resources.StyledAttributes, if set, or the TextAppearance_textColor
   6772      * from the TextView_textAppearance attribute, if TextView_textColor
   6773      * was not set directly.
   6774      */
   6775     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
   6776         ColorStateList colors;
   6777         colors = attrs.getColorStateList(com.android.internal.R.styleable.
   6778                                          TextView_textColor);
   6779 
   6780         if (colors == null) {
   6781             int ap = attrs.getResourceId(com.android.internal.R.styleable.
   6782                                          TextView_textAppearance, -1);
   6783             if (ap != -1) {
   6784                 TypedArray appearance;
   6785                 appearance = context.obtainStyledAttributes(ap,
   6786                                             com.android.internal.R.styleable.TextAppearance);
   6787                 colors = appearance.getColorStateList(com.android.internal.R.styleable.
   6788                                                   TextAppearance_textColor);
   6789                 appearance.recycle();
   6790             }
   6791         }
   6792 
   6793         return colors;
   6794     }
   6795 
   6796     /**
   6797      * Returns the default color from the TextView_textColor attribute
   6798      * from the AttributeSet, if set, or the default color from the
   6799      * TextAppearance_textColor from the TextView_textAppearance attribute,
   6800      * if TextView_textColor was not set directly.
   6801      */
   6802     public static int getTextColor(Context context,
   6803                                    TypedArray attrs,
   6804                                    int def) {
   6805         ColorStateList colors = getTextColors(context, attrs);
   6806 
   6807         if (colors == null) {
   6808             return def;
   6809         } else {
   6810             return colors.getDefaultColor();
   6811         }
   6812     }
   6813 
   6814     @Override
   6815     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
   6816         switch (keyCode) {
   6817         case KeyEvent.KEYCODE_A:
   6818             if (canSelectAll()) {
   6819                 return onTextContextMenuItem(ID_SELECT_ALL);
   6820             }
   6821 
   6822             break;
   6823 
   6824         case KeyEvent.KEYCODE_X:
   6825             if (canCut()) {
   6826                 return onTextContextMenuItem(ID_CUT);
   6827             }
   6828 
   6829             break;
   6830 
   6831         case KeyEvent.KEYCODE_C:
   6832             if (canCopy()) {
   6833                 return onTextContextMenuItem(ID_COPY);
   6834             }
   6835 
   6836             break;
   6837 
   6838         case KeyEvent.KEYCODE_V:
   6839             if (canPaste()) {
   6840                 return onTextContextMenuItem(ID_PASTE);
   6841             }
   6842 
   6843             break;
   6844         }
   6845 
   6846         return super.onKeyShortcut(keyCode, event);
   6847     }
   6848 
   6849     private boolean canSelectAll() {
   6850         if (mText instanceof Spannable && mText.length() != 0 &&
   6851             mMovement != null && mMovement.canSelectArbitrarily()) {
   6852             return true;
   6853         }
   6854 
   6855         return false;
   6856     }
   6857 
   6858     private boolean canSelectText() {
   6859         if (mText instanceof Spannable && mText.length() != 0 &&
   6860             mMovement != null && mMovement.canSelectArbitrarily()) {
   6861             return true;
   6862         }
   6863 
   6864         return false;
   6865     }
   6866 
   6867     private boolean canCut() {
   6868         if (mTransformation instanceof PasswordTransformationMethod) {
   6869             return false;
   6870         }
   6871 
   6872         if (mText.length() > 0 && getSelectionStart() >= 0) {
   6873             if (mText instanceof Editable && mInput != null) {
   6874                 return true;
   6875             }
   6876         }
   6877 
   6878         return false;
   6879     }
   6880 
   6881     private boolean canCopy() {
   6882         if (mTransformation instanceof PasswordTransformationMethod) {
   6883             return false;
   6884         }
   6885 
   6886         if (mText.length() > 0 && getSelectionStart() >= 0) {
   6887             return true;
   6888         }
   6889 
   6890         return false;
   6891     }
   6892 
   6893     private boolean canPaste() {
   6894         if (mText instanceof Editable && mInput != null &&
   6895             getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
   6896             ClipboardManager clip = (ClipboardManager)getContext()
   6897                     .getSystemService(Context.CLIPBOARD_SERVICE);
   6898             if (clip.hasText()) {
   6899                 return true;
   6900             }
   6901         }
   6902 
   6903         return false;
   6904     }
   6905 
   6906     /**
   6907      * Returns a word to add to the dictionary from the context menu,
   6908      * or null if there is no cursor or no word at the cursor.
   6909      */
   6910     private String getWordForDictionary() {
   6911         /*
   6912          * Quick return if the input type is one where adding words
   6913          * to the dictionary doesn't make any sense.
   6914          */
   6915         int klass = mInputType & InputType.TYPE_MASK_CLASS;
   6916         if (klass == InputType.TYPE_CLASS_NUMBER ||
   6917             klass == InputType.TYPE_CLASS_PHONE ||
   6918             klass == InputType.TYPE_CLASS_DATETIME) {
   6919             return null;
   6920         }
   6921 
   6922         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
   6923         if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
   6924             variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
   6925             variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
   6926             variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
   6927             variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
   6928             return null;
   6929         }
   6930 
   6931         int end = getSelectionEnd();
   6932 
   6933         if (end < 0) {
   6934             return null;
   6935         }
   6936 
   6937         int start = end;
   6938         int len = mText.length();
   6939 
   6940         for (; start > 0; start--) {
   6941             char c = mTransformed.charAt(start - 1);
   6942             int type = Character.getType(c);
   6943 
   6944             if (c != '\'' &&
   6945                 type != Character.UPPERCASE_LETTER &&
   6946                 type != Character.LOWERCASE_LETTER &&
   6947                 type != Character.TITLECASE_LETTER &&
   6948                 type != Character.MODIFIER_LETTER &&
   6949                 type != Character.DECIMAL_DIGIT_NUMBER) {
   6950                 break;
   6951             }
   6952         }
   6953 
   6954         for (; end < len; end++) {
   6955             char c = mTransformed.charAt(end);
   6956             int type = Character.getType(c);
   6957 
   6958             if (c != '\'' &&
   6959                 type != Character.UPPERCASE_LETTER &&
   6960                 type != Character.LOWERCASE_LETTER &&
   6961                 type != Character.TITLECASE_LETTER &&
   6962                 type != Character.MODIFIER_LETTER &&
   6963                 type != Character.DECIMAL_DIGIT_NUMBER) {
   6964                 break;
   6965             }
   6966         }
   6967 
   6968         boolean hasLetter = false;
   6969         for (int i = start; i < end; i++) {
   6970             if (Character.isLetter(mTransformed.charAt(i))) {
   6971                 hasLetter = true;
   6972                 break;
   6973             }
   6974         }
   6975         if (!hasLetter) {
   6976             return null;
   6977         }
   6978 
   6979         if (start == end) {
   6980             return null;
   6981         }
   6982 
   6983         if (end - start > 48) {
   6984             return null;
   6985         }
   6986 
   6987         return TextUtils.substring(mTransformed, start, end);
   6988     }
   6989 
   6990     @Override
   6991     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
   6992         if (!isShown()) {
   6993             return false;
   6994         }
   6995 
   6996         final boolean isPassword = isPasswordInputType(mInputType);
   6997 
   6998         if (!isPassword) {
   6999             CharSequence text = getText();
   7000             if (TextUtils.isEmpty(text)) {
   7001                 text = getHint();
   7002             }
   7003             if (!TextUtils.isEmpty(text)) {
   7004                 if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
   7005                     text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
   7006                 }
   7007                 event.getText().add(text);
   7008             }
   7009         } else {
   7010             event.setPassword(isPassword);
   7011         }
   7012         return false;
   7013     }
   7014 
   7015     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
   7016             int fromIndex, int removedCount, int addedCount) {
   7017         AccessibilityEvent event =
   7018             AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
   7019         event.setFromIndex(fromIndex);
   7020         event.setRemovedCount(removedCount);
   7021         event.setAddedCount(addedCount);
   7022         event.setBeforeText(beforeText);
   7023         sendAccessibilityEventUnchecked(event);
   7024     }
   7025 
   7026     @Override
   7027     protected void onCreateContextMenu(ContextMenu menu) {
   7028         super.onCreateContextMenu(menu);
   7029         boolean added = false;
   7030 
   7031         if (!isFocused()) {
   7032             if (isFocusable() && mInput != null) {
   7033                 if (canCopy()) {
   7034                     MenuHandler handler = new MenuHandler();
   7035                     int name = com.android.internal.R.string.copyAll;
   7036 
   7037                     menu.add(0, ID_COPY, 0, name).
   7038                         setOnMenuItemClickListener(handler).
   7039                         setAlphabeticShortcut('c');
   7040                     menu.setHeaderTitle(com.android.internal.R.string.
   7041                         editTextMenuTitle);
   7042                 }
   7043             }
   7044 
   7045             return;
   7046         }
   7047 
   7048         MenuHandler handler = new MenuHandler();
   7049 
   7050         if (canSelectAll()) {
   7051             menu.add(0, ID_SELECT_ALL, 0,
   7052                     com.android.internal.R.string.selectAll).
   7053                 setOnMenuItemClickListener(handler).
   7054                 setAlphabeticShortcut('a');
   7055             added = true;
   7056         }
   7057 
   7058         boolean selection = getSelectionStart() != getSelectionEnd();
   7059 
   7060         if (canSelectText()) {
   7061             if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
   7062                 menu.add(0, ID_STOP_SELECTING_TEXT, 0,
   7063                         com.android.internal.R.string.stopSelectingText).
   7064                     setOnMenuItemClickListener(handler);
   7065                 added = true;
   7066             } else {
   7067                 menu.add(0, ID_START_SELECTING_TEXT, 0,
   7068                         com.android.internal.R.string.selectText).
   7069                     setOnMenuItemClickListener(handler);
   7070                 added = true;
   7071             }
   7072         }
   7073 
   7074         if (canCut()) {
   7075             int name;
   7076             if (selection) {
   7077                 name = com.android.internal.R.string.cut;
   7078             } else {
   7079                 name = com.android.internal.R.string.cutAll;
   7080             }
   7081 
   7082             menu.add(0, ID_CUT, 0, name).
   7083                 setOnMenuItemClickListener(handler).
   7084                 setAlphabeticShortcut('x');
   7085             added = true;
   7086         }
   7087 
   7088         if (canCopy()) {
   7089             int name;
   7090             if (selection) {
   7091                 name = com.android.internal.R.string.copy;
   7092             } else {
   7093                 name = com.android.internal.R.string.copyAll;
   7094             }
   7095 
   7096             menu.add(0, ID_COPY, 0, name).
   7097                 setOnMenuItemClickListener(handler).
   7098                 setAlphabeticShortcut('c');
   7099             added = true;
   7100         }
   7101 
   7102         if (canPaste()) {
   7103             menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
   7104                     setOnMenuItemClickListener(handler).
   7105                     setAlphabeticShortcut('v');
   7106             added = true;
   7107         }
   7108 
   7109         if (mText instanceof Spanned) {
   7110             int selStart = getSelectionStart();
   7111             int selEnd = getSelectionEnd();
   7112 
   7113             int min = Math.min(selStart, selEnd);
   7114             int max = Math.max(selStart, selEnd);
   7115 
   7116             URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
   7117                                                         URLSpan.class);
   7118             if (urls.length == 1) {
   7119                 menu.add(0, ID_COPY_URL, 0,
   7120                          com.android.internal.R.string.copyUrl).
   7121                             setOnMenuItemClickListener(handler);
   7122                 added = true;
   7123             }
   7124         }
   7125 
   7126         if (isInputMethodTarget()) {
   7127             menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
   7128                     setOnMenuItemClickListener(handler);
   7129             added = true;
   7130         }
   7131 
   7132         String word = getWordForDictionary();
   7133         if (word != null) {
   7134             menu.add(1, ID_ADD_TO_DICTIONARY, 0,
   7135                      getContext().getString(com.android.internal.R.string.addToDictionary, word)).
   7136                     setOnMenuItemClickListener(handler);
   7137             added = true;
   7138 
   7139         }
   7140 
   7141         if (added) {
   7142             menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
   7143         }
   7144     }
   7145 
   7146     /**
   7147      * Returns whether this text view is a current input method target.  The
   7148      * default implementation just checks with {@link InputMethodManager}.
   7149      */
   7150     public boolean isInputMethodTarget() {
   7151         InputMethodManager imm = InputMethodManager.peekInstance();
   7152         return imm != null && imm.isActive(this);
   7153     }
   7154 
   7155     private static final int ID_SELECT_ALL = android.R.id.selectAll;
   7156     private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
   7157     private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
   7158     private static final int ID_CUT = android.R.id.cut;
   7159     private static final int ID_COPY = android.R.id.copy;
   7160     private static final int ID_PASTE = android.R.id.paste;
   7161     private static final int ID_COPY_URL = android.R.id.copyUrl;
   7162     private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
   7163     private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
   7164 
   7165     private class MenuHandler implements MenuItem.OnMenuItemClickListener {
   7166         public boolean onMenuItemClick(MenuItem item) {
   7167             return onTextContextMenuItem(item.getItemId());
   7168         }
   7169     }
   7170 
   7171     /**
   7172      * Called when a context menu option for the text view is selected.  Currently
   7173      * this will be one of: {@link android.R.id#selectAll},
   7174      * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
   7175      * {@link android.R.id#cut}, {@link android.R.id#copy},
   7176      * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
   7177      * or {@link android.R.id#switchInputMethod}.
   7178      */
   7179     public boolean onTextContextMenuItem(int id) {
   7180         int selStart = getSelectionStart();
   7181         int selEnd = getSelectionEnd();
   7182 
   7183         if (!isFocused()) {
   7184             selStart = 0;
   7185             selEnd = mText.length();
   7186         }
   7187 
   7188         int min = Math.min(selStart, selEnd);
   7189         int max = Math.max(selStart, selEnd);
   7190 
   7191         if (min < 0) {
   7192             min = 0;
   7193         }
   7194         if (max < 0) {
   7195             max = 0;
   7196         }
   7197 
   7198         ClipboardManager clip = (ClipboardManager)getContext()
   7199                 .getSystemService(Context.CLIPBOARD_SERVICE);
   7200 
   7201         switch (id) {
   7202             case ID_SELECT_ALL:
   7203                 Selection.setSelection((Spannable) mText, 0,
   7204                         mText.length());
   7205                 return true;
   7206 
   7207             case ID_START_SELECTING_TEXT:
   7208                 MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
   7209                 return true;
   7210 
   7211             case ID_STOP_SELECTING_TEXT:
   7212                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
   7213                 Selection.setSelection((Spannable) mText, getSelectionEnd());
   7214                 return true;
   7215 
   7216             case ID_CUT:
   7217                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
   7218 
   7219                 if (min == max) {
   7220                     min = 0;
   7221                     max = mText.length();
   7222                 }
   7223 
   7224                 clip.setText(mTransformed.subSequence(min, max));
   7225                 ((Editable) mText).delete(min, max);
   7226                 return true;
   7227 
   7228             case ID_COPY:
   7229                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
   7230 
   7231                 if (min == max) {
   7232                     min = 0;
   7233                     max = mText.length();
   7234                 }
   7235 
   7236                 clip.setText(mTransformed.subSequence(min, max));
   7237                 return true;
   7238 
   7239             case ID_PASTE:
   7240                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
   7241 
   7242                 CharSequence paste = clip.getText();
   7243 
   7244                 if (paste != null) {
   7245                     Selection.setSelection((Spannable) mText, max);
   7246                     ((Editable) mText).replace(min, max, paste);
   7247                 }
   7248 
   7249                 return true;
   7250 
   7251             case ID_COPY_URL:
   7252                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
   7253 
   7254                 URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
   7255                                                        URLSpan.class);
   7256                 if (urls.length == 1) {
   7257                     clip.setText(urls[0].getURL());
   7258                 }
   7259 
   7260                 return true;
   7261 
   7262             case ID_SWITCH_INPUT_METHOD:
   7263                 InputMethodManager imm = InputMethodManager.peekInstance();
   7264                 if (imm != null) {
   7265                     imm.showInputMethodPicker();
   7266                 }
   7267                 return true;
   7268 
   7269             case ID_ADD_TO_DICTIONARY:
   7270                 String word = getWordForDictionary();
   7271 
   7272                 if (word != null) {
   7273                     Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
   7274                     i.putExtra("word", word);
   7275                     i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
   7276                     getContext().startActivity(i);
   7277                 }
   7278 
   7279                 return true;
   7280             }
   7281 
   7282         return false;
   7283     }
   7284 
   7285     public boolean performLongClick() {
   7286         if (super.performLongClick()) {
   7287             mEatTouchRelease = true;
   7288             return true;
   7289         }
   7290 
   7291         return false;
   7292     }
   7293 
   7294     @ViewDebug.ExportedProperty
   7295     private CharSequence            mText;
   7296     private CharSequence            mTransformed;
   7297     private BufferType              mBufferType = BufferType.NORMAL;
   7298 
   7299     private int                     mInputType = EditorInfo.TYPE_NULL;
   7300     private CharSequence            mHint;
   7301     private Layout                  mHintLayout;
   7302 
   7303     private KeyListener             mInput;
   7304 
   7305     private MovementMethod          mMovement;
   7306     private TransformationMethod    mTransformation;
   7307     private ChangeWatcher           mChangeWatcher;
   7308 
   7309     private ArrayList<TextWatcher>  mListeners = null;
   7310 
   7311     // display attributes
   7312     private TextPaint               mTextPaint;
   7313     private boolean                 mUserSetTextScaleX;
   7314     private Paint                   mHighlightPaint;
   7315     private int                     mHighlightColor = 0xFFBBDDFF;
   7316     private Layout                  mLayout;
   7317 
   7318     private long                    mShowCursor;
   7319     private Blink                   mBlink;
   7320     private boolean                 mCursorVisible = true;
   7321 
   7322     private boolean                 mSelectAllOnFocus = false;
   7323 
   7324     private int                     mGravity = Gravity.TOP | Gravity.LEFT;
   7325     private boolean                 mHorizontallyScrolling;
   7326 
   7327     private int                     mAutoLinkMask;
   7328     private boolean                 mLinksClickable = true;
   7329 
   7330     private float                   mSpacingMult = 1;
   7331     private float                   mSpacingAdd = 0;
   7332 
   7333     private static final int        LINES = 1;
   7334     private static final int        EMS = LINES;
   7335     private static final int        PIXELS = 2;
   7336 
   7337     private int                     mMaximum = Integer.MAX_VALUE;
   7338     private int                     mMaxMode = LINES;
   7339     private int                     mMinimum = 0;
   7340     private int                     mMinMode = LINES;
   7341 
   7342     private int                     mMaxWidth = Integer.MAX_VALUE;
   7343     private int                     mMaxWidthMode = PIXELS;
   7344     private int                     mMinWidth = 0;
   7345     private int                     mMinWidthMode = PIXELS;
   7346 
   7347     private boolean                 mSingleLine;
   7348     private int                     mDesiredHeightAtMeasure = -1;
   7349     private boolean                 mIncludePad = true;
   7350 
   7351     // tmp primitives, so we don't alloc them on each draw
   7352     private Path                    mHighlightPath;
   7353     private boolean                 mHighlightPathBogus = true;
   7354     private static final RectF      sTempRect = new RectF();
   7355 
   7356     // XXX should be much larger
   7357     private static final int        VERY_WIDE = 16384;
   7358 
   7359     private static final int        BLINK = 500;
   7360 
   7361     private static final int ANIMATED_SCROLL_GAP = 250;
   7362     private long mLastScroll;
   7363     private Scroller mScroller = null;
   7364 
   7365     private BoringLayout.Metrics mBoring;
   7366     private BoringLayout.Metrics mHintBoring;
   7367 
   7368     private BoringLayout mSavedLayout, mSavedHintLayout;
   7369 
   7370     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   7371     private InputFilter[] mFilters = NO_FILTERS;
   7372     private static final Spanned EMPTY_SPANNED = new SpannedString("");
   7373 }
   7374