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