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