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