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