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