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                     }
   7787                 } else {
   7788                     if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
   7789                             + oldStart + "-" + oldEnd + ","
   7790                             + newStart + "-" + newEnd + what);
   7791                     ims.mContentChanged = true;
   7792                 }
   7793             }
   7794         }
   7795 
   7796         if (mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
   7797             mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
   7798         }
   7799     }
   7800 
   7801     /**
   7802      * Create new SpellCheckSpans on the modified region.
   7803      */
   7804     private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
   7805         if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
   7806             if (mSpellChecker == null && createSpellChecker) {
   7807                 mSpellChecker = new SpellChecker(this);
   7808             }
   7809             if (mSpellChecker != null) {
   7810                 mSpellChecker.spellCheck(start, end);
   7811             }
   7812         }
   7813     }
   7814 
   7815     /**
   7816      * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
   7817      * pop-up should be displayed.
   7818      */
   7819     private class EasyEditSpanController {
   7820 
   7821         private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
   7822 
   7823         private EasyEditPopupWindow mPopupWindow;
   7824 
   7825         private EasyEditSpan mEasyEditSpan;
   7826 
   7827         private Runnable mHidePopup;
   7828 
   7829         private void hide() {
   7830             if (mPopupWindow != null) {
   7831                 mPopupWindow.hide();
   7832                 TextView.this.removeCallbacks(mHidePopup);
   7833             }
   7834             removeSpans(mText);
   7835             mEasyEditSpan = null;
   7836         }
   7837 
   7838         /**
   7839          * Monitors the changes in the text.
   7840          *
   7841          * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
   7842          * as the notifications are not sent when a spannable (with spans) is inserted.
   7843          */
   7844         public void onTextChange(CharSequence buffer) {
   7845             adjustSpans(mText);
   7846 
   7847             if (getWindowVisibility() != View.VISIBLE) {
   7848                 // The window is not visible yet, ignore the text change.
   7849                 return;
   7850             }
   7851 
   7852             if (mLayout == null) {
   7853                 // The view has not been layout yet, ignore the text change
   7854                 return;
   7855             }
   7856 
   7857             InputMethodManager imm = InputMethodManager.peekInstance();
   7858             if (!(TextView.this instanceof ExtractEditText)
   7859                     && imm != null && imm.isFullscreenMode()) {
   7860                 // The input is in extract mode. We do not have to handle the easy edit in the
   7861                 // original TextView, as the ExtractEditText will do
   7862                 return;
   7863             }
   7864 
   7865             // Remove the current easy edit span, as the text changed, and remove the pop-up
   7866             // (if any)
   7867             if (mEasyEditSpan != null) {
   7868                 if (mText instanceof Spannable) {
   7869                     ((Spannable) mText).removeSpan(mEasyEditSpan);
   7870                 }
   7871                 mEasyEditSpan = null;
   7872             }
   7873             if (mPopupWindow != null && mPopupWindow.isShowing()) {
   7874                 mPopupWindow.hide();
   7875             }
   7876 
   7877             // Display the new easy edit span (if any).
   7878             if (buffer instanceof Spanned) {
   7879                 mEasyEditSpan = getSpan((Spanned) buffer);
   7880                 if (mEasyEditSpan != null) {
   7881                     if (mPopupWindow == null) {
   7882                         mPopupWindow = new EasyEditPopupWindow();
   7883                         mHidePopup = new Runnable() {
   7884                             @Override
   7885                             public void run() {
   7886                                 hide();
   7887                             }
   7888                         };
   7889                     }
   7890                     mPopupWindow.show(mEasyEditSpan);
   7891                     TextView.this.removeCallbacks(mHidePopup);
   7892                     TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
   7893                 }
   7894             }
   7895         }
   7896 
   7897         /**
   7898          * Adjusts the spans by removing all of them except the last one.
   7899          */
   7900         private void adjustSpans(CharSequence buffer) {
   7901             // This method enforces that only one easy edit span is attached to the text.
   7902             // A better way to enforce this would be to listen for onSpanAdded, but this method
   7903             // cannot be used in this scenario as no notification is triggered when a text with
   7904             // spans is inserted into a text.
   7905             if (buffer instanceof Spannable) {
   7906                 Spannable spannable = (Spannable) buffer;
   7907                 EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
   7908                         EasyEditSpan.class);
   7909                 for (int i = 0; i < spans.length - 1; i++) {
   7910                     spannable.removeSpan(spans[i]);
   7911                 }
   7912             }
   7913         }
   7914 
   7915         /**
   7916          * Removes all the {@link EasyEditSpan} currently attached.
   7917          */
   7918         private void removeSpans(CharSequence buffer) {
   7919             if (buffer instanceof Spannable) {
   7920                 Spannable spannable = (Spannable) buffer;
   7921                 EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
   7922                         EasyEditSpan.class);
   7923                 for (int i = 0; i < spans.length; i++) {
   7924                     spannable.removeSpan(spans[i]);
   7925                 }
   7926             }
   7927         }
   7928 
   7929         private EasyEditSpan getSpan(Spanned spanned) {
   7930             EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
   7931                     EasyEditSpan.class);
   7932             if (easyEditSpans.length == 0) {
   7933                 return null;
   7934             } else {
   7935                 return easyEditSpans[0];
   7936             }
   7937         }
   7938     }
   7939 
   7940     /**
   7941      * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
   7942      * by {@link EasyEditSpanController}.
   7943      */
   7944     private class EasyEditPopupWindow extends PinnedPopupWindow
   7945             implements OnClickListener {
   7946         private static final int POPUP_TEXT_LAYOUT =
   7947                 com.android.internal.R.layout.text_edit_action_popup_text;
   7948         private TextView mDeleteTextView;
   7949         private EasyEditSpan mEasyEditSpan;
   7950 
   7951         @Override
   7952         protected void createPopupWindow() {
   7953             mPopupWindow = new PopupWindow(TextView.this.mContext, null,
   7954                     com.android.internal.R.attr.textSelectHandleWindowStyle);
   7955             mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   7956             mPopupWindow.setClippingEnabled(true);
   7957         }
   7958 
   7959         @Override
   7960         protected void initContentView() {
   7961             LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
   7962             linearLayout.setOrientation(LinearLayout.HORIZONTAL);
   7963             mContentView = linearLayout;
   7964             mContentView.setBackgroundResource(
   7965                     com.android.internal.R.drawable.text_edit_side_paste_window);
   7966 
   7967             LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
   7968                     getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   7969 
   7970             LayoutParams wrapContent = new LayoutParams(
   7971                     ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
   7972 
   7973             mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
   7974             mDeleteTextView.setLayoutParams(wrapContent);
   7975             mDeleteTextView.setText(com.android.internal.R.string.delete);
   7976             mDeleteTextView.setOnClickListener(this);
   7977             mContentView.addView(mDeleteTextView);
   7978         }
   7979 
   7980         public void show(EasyEditSpan easyEditSpan) {
   7981             mEasyEditSpan = easyEditSpan;
   7982             super.show();
   7983         }
   7984 
   7985         @Override
   7986         public void onClick(View view) {
   7987             if (view == mDeleteTextView) {
   7988                 Editable editable = (Editable) mText;
   7989                 int start = editable.getSpanStart(mEasyEditSpan);
   7990                 int end = editable.getSpanEnd(mEasyEditSpan);
   7991                 if (start >= 0 && end >= 0) {
   7992                     deleteText_internal(start, end);
   7993                 }
   7994             }
   7995         }
   7996 
   7997         @Override
   7998         protected int getTextOffset() {
   7999             // Place the pop-up at the end of the span
   8000             Editable editable = (Editable) mText;
   8001             return editable.getSpanEnd(mEasyEditSpan);
   8002         }
   8003 
   8004         @Override
   8005         protected int getVerticalLocalPosition(int line) {
   8006             return mLayout.getLineBottom(line);
   8007         }
   8008 
   8009         @Override
   8010         protected int clipVertically(int positionY) {
   8011             // As we display the pop-up below the span, no vertical clipping is required.
   8012             return positionY;
   8013         }
   8014     }
   8015 
   8016     private class ChangeWatcher implements TextWatcher, SpanWatcher {
   8017 
   8018         private CharSequence mBeforeText;
   8019 
   8020         private EasyEditSpanController mEasyEditSpanController;
   8021 
   8022         private ChangeWatcher() {
   8023             mEasyEditSpanController = new EasyEditSpanController();
   8024         }
   8025 
   8026         public void beforeTextChanged(CharSequence buffer, int start,
   8027                                       int before, int after) {
   8028             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
   8029                     + " before=" + before + " after=" + after + ": " + buffer);
   8030 
   8031             if (AccessibilityManager.getInstance(mContext).isEnabled()
   8032                     && !isPasswordInputType(mInputType)
   8033                     && !hasPasswordTransformationMethod()) {
   8034                 mBeforeText = buffer.toString();
   8035             }
   8036 
   8037             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
   8038         }
   8039 
   8040         public void onTextChanged(CharSequence buffer, int start,
   8041                                   int before, int after) {
   8042             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
   8043                     + " before=" + before + " after=" + after + ": " + buffer);
   8044             TextView.this.handleTextChanged(buffer, start, before, after);
   8045 
   8046             mEasyEditSpanController.onTextChange(buffer);
   8047 
   8048             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
   8049                     (isFocused() || isSelected() && isShown())) {
   8050                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
   8051                 mBeforeText = null;
   8052             }
   8053         }
   8054 
   8055         public void afterTextChanged(Editable buffer) {
   8056             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
   8057             TextView.this.sendAfterTextChanged(buffer);
   8058 
   8059             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
   8060                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
   8061             }
   8062         }
   8063 
   8064         public void onSpanChanged(Spannable buf,
   8065                                   Object what, int s, int e, int st, int en) {
   8066             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
   8067                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
   8068             TextView.this.spanChange(buf, what, s, st, e, en);
   8069         }
   8070 
   8071         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
   8072             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
   8073                     + " what=" + what + ": " + buf);
   8074             TextView.this.spanChange(buf, what, -1, s, -1, e);
   8075         }
   8076 
   8077         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
   8078             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
   8079                     + " what=" + what + ": " + buf);
   8080             TextView.this.spanChange(buf, what, s, -1, e, -1);
   8081         }
   8082 
   8083         private void hideControllers() {
   8084             mEasyEditSpanController.hide();
   8085         }
   8086     }
   8087 
   8088     /**
   8089      * @hide
   8090      */
   8091     @Override
   8092     public void dispatchFinishTemporaryDetach() {
   8093         mDispatchTemporaryDetach = true;
   8094         super.dispatchFinishTemporaryDetach();
   8095         mDispatchTemporaryDetach = false;
   8096     }
   8097 
   8098     @Override
   8099     public void onStartTemporaryDetach() {
   8100         super.onStartTemporaryDetach();
   8101         // Only track when onStartTemporaryDetach() is called directly,
   8102         // usually because this instance is an editable field in a list
   8103         if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
   8104 
   8105         // Because of View recycling in ListView, there is no easy way to know when a TextView with
   8106         // selection becomes visible again. Until a better solution is found, stop text selection
   8107         // mode (if any) as soon as this TextView is recycled.
   8108         hideControllers();
   8109     }
   8110 
   8111     @Override
   8112     public void onFinishTemporaryDetach() {
   8113         super.onFinishTemporaryDetach();
   8114         // Only track when onStartTemporaryDetach() is called directly,
   8115         // usually because this instance is an editable field in a list
   8116         if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
   8117     }
   8118 
   8119     @Override
   8120     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
   8121         if (mTemporaryDetach) {
   8122             // If we are temporarily in the detach state, then do nothing.
   8123             super.onFocusChanged(focused, direction, previouslyFocusedRect);
   8124             return;
   8125         }
   8126 
   8127         mShowCursor = SystemClock.uptimeMillis();
   8128 
   8129         ensureEndedBatchEdit();
   8130 
   8131         if (focused) {
   8132             int selStart = getSelectionStart();
   8133             int selEnd = getSelectionEnd();
   8134 
   8135             // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
   8136             // mode for these, unless there was a specific selection already started.
   8137             final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
   8138                     selEnd == mText.length();
   8139             mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
   8140 
   8141             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
   8142                 // If a tap was used to give focus to that view, move cursor at tap position.
   8143                 // Has to be done before onTakeFocus, which can be overloaded.
   8144                 final int lastTapPosition = getLastTapPosition();
   8145                 if (lastTapPosition >= 0) {
   8146                     Selection.setSelection((Spannable) mText, lastTapPosition);
   8147                 }
   8148 
   8149                 if (mMovement != null) {
   8150                     mMovement.onTakeFocus(this, (Spannable) mText, direction);
   8151                 }
   8152 
   8153                 // The DecorView does not have focus when the 'Done' ExtractEditText button is
   8154                 // pressed. Since it is the ViewAncestor's mView, it requests focus before
   8155                 // ExtractEditText clears focus, which gives focus to the ExtractEditText.
   8156                 // This special case ensure that we keep current selection in that case.
   8157                 // It would be better to know why the DecorView does not have focus at that time.
   8158                 if (((this instanceof ExtractEditText) || mSelectionMoved) &&
   8159                         selStart >= 0 && selEnd >= 0) {
   8160                     /*
   8161                      * Someone intentionally set the selection, so let them
   8162                      * do whatever it is that they wanted to do instead of
   8163                      * the default on-focus behavior.  We reset the selection
   8164                      * here instead of just skipping the onTakeFocus() call
   8165                      * because some movement methods do something other than
   8166                      * just setting the selection in theirs and we still
   8167                      * need to go through that path.
   8168                      */
   8169                     Selection.setSelection((Spannable) mText, selStart, selEnd);
   8170                 }
   8171 
   8172                 if (mSelectAllOnFocus) {
   8173                     selectAll();
   8174                 }
   8175 
   8176                 mTouchFocusSelected = true;
   8177             }
   8178 
   8179             mFrozenWithFocus = false;
   8180             mSelectionMoved = false;
   8181 
   8182             if (mText instanceof Spannable) {
   8183                 Spannable sp = (Spannable) mText;
   8184                 MetaKeyKeyListener.resetMetaState(sp);
   8185             }
   8186 
   8187             makeBlink();
   8188 
   8189             if (mError != null) {
   8190                 showError();
   8191             }
   8192         } else {
   8193             if (mError != null) {
   8194                 hideError();
   8195             }
   8196             // Don't leave us in the middle of a batch edit.
   8197             onEndBatchEdit();
   8198 
   8199             if (this instanceof ExtractEditText) {
   8200                 // terminateTextSelectionMode removes selection, which we want to keep when
   8201                 // ExtractEditText goes out of focus.
   8202                 final int selStart = getSelectionStart();
   8203                 final int selEnd = getSelectionEnd();
   8204                 hideControllers();
   8205                 Selection.setSelection((Spannable) mText, selStart, selEnd);
   8206             } else {
   8207                 hideControllers();
   8208                 downgradeEasyCorrectionSpans();
   8209             }
   8210 
   8211             // No need to create the controller
   8212             if (mSelectionModifierCursorController != null) {
   8213                 mSelectionModifierCursorController.resetTouchOffsets();
   8214             }
   8215         }
   8216 
   8217         startStopMarquee(focused);
   8218 
   8219         if (mTransformation != null) {
   8220             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
   8221         }
   8222 
   8223         super.onFocusChanged(focused, direction, previouslyFocusedRect);
   8224     }
   8225 
   8226     private int getLastTapPosition() {
   8227         // No need to create the controller at that point, no last tap position saved
   8228         if (mSelectionModifierCursorController != null) {
   8229             int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
   8230             if (lastTapPosition >= 0) {
   8231                 // Safety check, should not be possible.
   8232                 if (lastTapPosition > mText.length()) {
   8233                     Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
   8234                             + mText.length() + ")");
   8235                     lastTapPosition = mText.length();
   8236                 }
   8237                 return lastTapPosition;
   8238             }
   8239         }
   8240 
   8241         return -1;
   8242     }
   8243 
   8244     @Override
   8245     public void onWindowFocusChanged(boolean hasWindowFocus) {
   8246         super.onWindowFocusChanged(hasWindowFocus);
   8247 
   8248         if (hasWindowFocus) {
   8249             if (mBlink != null) {
   8250                 mBlink.uncancel();
   8251                 makeBlink();
   8252             }
   8253         } else {
   8254             if (mBlink != null) {
   8255                 mBlink.cancel();
   8256             }
   8257             // Don't leave us in the middle of a batch edit.
   8258             onEndBatchEdit();
   8259             if (mInputContentType != null) {
   8260                 mInputContentType.enterDown = false;
   8261             }
   8262 
   8263             hideControllers();
   8264             if (mSuggestionsPopupWindow != null) {
   8265                 mSuggestionsPopupWindow.onParentLostFocus();
   8266             }
   8267         }
   8268 
   8269         startStopMarquee(hasWindowFocus);
   8270     }
   8271 
   8272     @Override
   8273     protected void onVisibilityChanged(View changedView, int visibility) {
   8274         super.onVisibilityChanged(changedView, visibility);
   8275         if (visibility != VISIBLE) {
   8276             hideControllers();
   8277         }
   8278     }
   8279 
   8280     /**
   8281      * Use {@link BaseInputConnection#removeComposingSpans
   8282      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
   8283      * state from this text view.
   8284      */
   8285     public void clearComposingText() {
   8286         if (mText instanceof Spannable) {
   8287             BaseInputConnection.removeComposingSpans((Spannable)mText);
   8288         }
   8289     }
   8290 
   8291     @Override
   8292     public void setSelected(boolean selected) {
   8293         boolean wasSelected = isSelected();
   8294 
   8295         super.setSelected(selected);
   8296 
   8297         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   8298             if (selected) {
   8299                 startMarquee();
   8300             } else {
   8301                 stopMarquee();
   8302             }
   8303         }
   8304     }
   8305 
   8306     @Override
   8307     public boolean onTouchEvent(MotionEvent event) {
   8308         final int action = event.getActionMasked();
   8309 
   8310         if (hasSelectionController()) {
   8311             getSelectionController().onTouchEvent(event);
   8312         }
   8313 
   8314         if (action == MotionEvent.ACTION_DOWN) {
   8315             mLastDownPositionX = event.getX();
   8316             mLastDownPositionY = event.getY();
   8317 
   8318             // Reset this state; it will be re-set if super.onTouchEvent
   8319             // causes focus to move to the view.
   8320             mTouchFocusSelected = false;
   8321             mIgnoreActionUpEvent = false;
   8322         }
   8323 
   8324         final boolean superResult = super.onTouchEvent(event);
   8325 
   8326         /*
   8327          * Don't handle the release after a long press, because it will
   8328          * move the selection away from whatever the menu action was
   8329          * trying to affect.
   8330          */
   8331         if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
   8332             mDiscardNextActionUp = false;
   8333             return superResult;
   8334         }
   8335 
   8336         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
   8337                 !shouldIgnoreActionUpEvent() && isFocused();
   8338 
   8339          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
   8340                 && mText instanceof Spannable && mLayout != null) {
   8341             boolean handled = false;
   8342 
   8343             if (mMovement != null) {
   8344                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
   8345             }
   8346 
   8347             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
   8348                 // The LinkMovementMethod which should handle taps on links has not been installed
   8349                 // on non editable text that support text selection.
   8350                 // We reproduce its behavior here to open links for these.
   8351                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
   8352                         getSelectionEnd(), ClickableSpan.class);
   8353 
   8354                 if (links.length != 0) {
   8355                     links[0].onClick(this);
   8356                     handled = true;
   8357                 }
   8358             }
   8359 
   8360             if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
   8361                 // Show the IME, except when selecting in read-only text.
   8362                 final InputMethodManager imm = InputMethodManager.peekInstance();
   8363                 viewClicked(imm);
   8364                 if (!mTextIsSelectable && mSoftInputShownOnFocus) {
   8365                     handled |= imm != null && imm.showSoftInput(this, 0);
   8366                 }
   8367 
   8368                 boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
   8369                 hideControllers();
   8370                 if (!selectAllGotFocus && mText.length() > 0) {
   8371                     if (mSpellChecker != null) {
   8372                         // When the cursor moves, the word that was typed may need spell check
   8373                         mSpellChecker.onSelectionChanged();
   8374                     }
   8375                     if (!extractedTextModeWillBeStarted()) {
   8376                         if (isCursorInsideEasyCorrectionSpan()) {
   8377                             showSuggestions();
   8378                         } else if (hasInsertionController()) {
   8379                             getInsertionController().show();
   8380                         }
   8381                     }
   8382                 }
   8383 
   8384                 handled = true;
   8385             }
   8386 
   8387             if (handled) {
   8388                 return true;
   8389             }
   8390         }
   8391 
   8392         return superResult;
   8393     }
   8394 
   8395     /**
   8396      * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}.
   8397      */
   8398     private boolean isCursorInsideSuggestionSpan() {
   8399         if (!(mText instanceof Spannable)) return false;
   8400 
   8401         SuggestionSpan[] suggestionSpans = ((Spannable) mText).getSpans(getSelectionStart(),
   8402                 getSelectionEnd(), SuggestionSpan.class);
   8403         return (suggestionSpans.length > 0);
   8404     }
   8405 
   8406     /**
   8407      * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with
   8408      * {@link SuggestionSpan#FLAG_EASY_CORRECT} set.
   8409      */
   8410     private boolean isCursorInsideEasyCorrectionSpan() {
   8411         Spannable spannable = (Spannable) mText;
   8412         SuggestionSpan[] suggestionSpans = spannable.getSpans(getSelectionStart(),
   8413                 getSelectionEnd(), SuggestionSpan.class);
   8414         for (int i = 0; i < suggestionSpans.length; i++) {
   8415             if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) {
   8416                 return true;
   8417             }
   8418         }
   8419         return false;
   8420     }
   8421 
   8422     /**
   8423      * Downgrades to simple suggestions all the easy correction spans that are not a spell check
   8424      * span.
   8425      */
   8426     private void downgradeEasyCorrectionSpans() {
   8427         if (mText instanceof Spannable) {
   8428             Spannable spannable = (Spannable) mText;
   8429             SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
   8430                     spannable.length(), SuggestionSpan.class);
   8431             for (int i = 0; i < suggestionSpans.length; i++) {
   8432                 int flags = suggestionSpans[i].getFlags();
   8433                 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
   8434                         && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) {
   8435                     flags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
   8436                     suggestionSpans[i].setFlags(flags);
   8437                 }
   8438             }
   8439         }
   8440     }
   8441 
   8442     @Override
   8443     public boolean onGenericMotionEvent(MotionEvent event) {
   8444         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
   8445             try {
   8446                 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
   8447                     return true;
   8448                 }
   8449             } catch (AbstractMethodError ex) {
   8450                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
   8451                 // Ignore its absence in case third party applications implemented the
   8452                 // interface directly.
   8453             }
   8454         }
   8455         return super.onGenericMotionEvent(event);
   8456     }
   8457 
   8458     private void prepareCursorControllers() {
   8459         boolean windowSupportsHandles = false;
   8460 
   8461         ViewGroup.LayoutParams params = getRootView().getLayoutParams();
   8462         if (params instanceof WindowManager.LayoutParams) {
   8463             WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
   8464             windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
   8465                     || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
   8466         }
   8467 
   8468         mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
   8469         mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
   8470                 mLayout != null;
   8471 
   8472         if (!mInsertionControllerEnabled) {
   8473             hideInsertionPointCursorController();
   8474             if (mInsertionPointCursorController != null) {
   8475                 mInsertionPointCursorController.onDetached();
   8476                 mInsertionPointCursorController = null;
   8477             }
   8478         }
   8479 
   8480         if (!mSelectionControllerEnabled) {
   8481             stopSelectionActionMode();
   8482             if (mSelectionModifierCursorController != null) {
   8483                 mSelectionModifierCursorController.onDetached();
   8484                 mSelectionModifierCursorController = null;
   8485             }
   8486         }
   8487     }
   8488 
   8489     /**
   8490      * @return True iff this TextView contains a text that can be edited, or if this is
   8491      * a selectable TextView.
   8492      */
   8493     private boolean isTextEditable() {
   8494         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
   8495     }
   8496 
   8497     /**
   8498      * Returns true, only while processing a touch gesture, if the initial
   8499      * touch down event caused focus to move to the text view and as a result
   8500      * its selection changed.  Only valid while processing the touch gesture
   8501      * of interest.
   8502      */
   8503     public boolean didTouchFocusSelect() {
   8504         return mTouchFocusSelected;
   8505     }
   8506 
   8507     @Override
   8508     public void cancelLongPress() {
   8509         super.cancelLongPress();
   8510         mIgnoreActionUpEvent = true;
   8511     }
   8512 
   8513     /**
   8514      * This method is only valid during a touch event.
   8515      *
   8516      * @return true when the ACTION_UP event should be ignored, false otherwise.
   8517      *
   8518      * @hide
   8519      */
   8520     public boolean shouldIgnoreActionUpEvent() {
   8521         return mIgnoreActionUpEvent;
   8522     }
   8523 
   8524     @Override
   8525     public boolean onTrackballEvent(MotionEvent event) {
   8526         if (mMovement != null && mText instanceof Spannable &&
   8527             mLayout != null) {
   8528             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
   8529                 return true;
   8530             }
   8531         }
   8532 
   8533         return super.onTrackballEvent(event);
   8534     }
   8535 
   8536     public void setScroller(Scroller s) {
   8537         mScroller = s;
   8538     }
   8539 
   8540     private static class Blink extends Handler implements Runnable {
   8541         private final WeakReference<TextView> mView;
   8542         private boolean mCancelled;
   8543 
   8544         public Blink(TextView v) {
   8545             mView = new WeakReference<TextView>(v);
   8546         }
   8547 
   8548         public void run() {
   8549             if (mCancelled) {
   8550                 return;
   8551             }
   8552 
   8553             removeCallbacks(Blink.this);
   8554 
   8555             TextView tv = mView.get();
   8556 
   8557             if (tv != null && tv.shouldBlink()) {
   8558                 if (tv.mLayout != null) {
   8559                     tv.invalidateCursorPath();
   8560                 }
   8561 
   8562                 postAtTime(this, SystemClock.uptimeMillis() + BLINK);
   8563             }
   8564         }
   8565 
   8566         void cancel() {
   8567             if (!mCancelled) {
   8568                 removeCallbacks(Blink.this);
   8569                 mCancelled = true;
   8570             }
   8571         }
   8572 
   8573         void uncancel() {
   8574             mCancelled = false;
   8575         }
   8576     }
   8577 
   8578     /**
   8579      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
   8580      */
   8581     private boolean shouldBlink() {
   8582         if (!isFocused()) return false;
   8583 
   8584         final int start = getSelectionStart();
   8585         if (start < 0) return false;
   8586 
   8587         final int end = getSelectionEnd();
   8588         if (end < 0) return false;
   8589 
   8590         return start == end;
   8591     }
   8592 
   8593     private void makeBlink() {
   8594         if (isCursorVisible()) {
   8595             if (shouldBlink()) {
   8596                 mShowCursor = SystemClock.uptimeMillis();
   8597                 if (mBlink == null) mBlink = new Blink(this);
   8598                 mBlink.removeCallbacks(mBlink);
   8599                 mBlink.postAtTime(mBlink, mShowCursor + BLINK);
   8600             }
   8601         } else {
   8602             if (mBlink != null) mBlink.removeCallbacks(mBlink);
   8603         }
   8604     }
   8605 
   8606     @Override
   8607     protected float getLeftFadingEdgeStrength() {
   8608         if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
   8609         if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
   8610                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   8611             if (mMarquee != null && !mMarquee.isStopped()) {
   8612                 final Marquee marquee = mMarquee;
   8613                 if (marquee.shouldDrawLeftFade()) {
   8614                     return marquee.mScroll / getHorizontalFadingEdgeLength();
   8615                 } else {
   8616                     return 0.0f;
   8617                 }
   8618             } else if (getLineCount() == 1) {
   8619                 final int layoutDirection = getResolvedLayoutDirection();
   8620                 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
   8621                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   8622                     case Gravity.LEFT:
   8623                         return 0.0f;
   8624                     case Gravity.RIGHT:
   8625                         return (mLayout.getLineRight(0) - (mRight - mLeft) -
   8626                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
   8627                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
   8628                     case Gravity.CENTER_HORIZONTAL:
   8629                         return 0.0f;
   8630                 }
   8631             }
   8632         }
   8633         return super.getLeftFadingEdgeStrength();
   8634     }
   8635 
   8636     @Override
   8637     protected float getRightFadingEdgeStrength() {
   8638         if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f;
   8639         if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
   8640                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   8641             if (mMarquee != null && !mMarquee.isStopped()) {
   8642                 final Marquee marquee = mMarquee;
   8643                 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
   8644             } else if (getLineCount() == 1) {
   8645                 final int layoutDirection = getResolvedLayoutDirection();
   8646                 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
   8647                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
   8648                     case Gravity.LEFT:
   8649                         final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
   8650                                 getCompoundPaddingRight();
   8651                         final float lineWidth = mLayout.getLineWidth(0);
   8652                         return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
   8653                     case Gravity.RIGHT:
   8654                         return 0.0f;
   8655                     case Gravity.CENTER_HORIZONTAL:
   8656                     case Gravity.FILL_HORIZONTAL:
   8657                         return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
   8658                                 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
   8659                                 getHorizontalFadingEdgeLength();
   8660                 }
   8661             }
   8662         }
   8663         return super.getRightFadingEdgeStrength();
   8664     }
   8665 
   8666     @Override
   8667     protected int computeHorizontalScrollRange() {
   8668         if (mLayout != null) {
   8669             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
   8670                     (int) mLayout.getLineWidth(0) : mLayout.getWidth();
   8671         }
   8672 
   8673         return super.computeHorizontalScrollRange();
   8674     }
   8675 
   8676     @Override
   8677     protected int computeVerticalScrollRange() {
   8678         if (mLayout != null)
   8679             return mLayout.getHeight();
   8680 
   8681         return super.computeVerticalScrollRange();
   8682     }
   8683 
   8684     @Override
   8685     protected int computeVerticalScrollExtent() {
   8686         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
   8687     }
   8688 
   8689     @Override
   8690     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
   8691         super.findViewsWithText(outViews, searched, flags);
   8692         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
   8693                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
   8694             String searchedLowerCase = searched.toString().toLowerCase();
   8695             String textLowerCase = mText.toString().toLowerCase();
   8696             if (textLowerCase.contains(searchedLowerCase)) {
   8697                 outViews.add(this);
   8698             }
   8699         }
   8700     }
   8701 
   8702     public enum BufferType {
   8703         NORMAL, SPANNABLE, EDITABLE,
   8704     }
   8705 
   8706     /**
   8707      * Returns the TextView_textColor attribute from the
   8708      * Resources.StyledAttributes, if set, or the TextAppearance_textColor
   8709      * from the TextView_textAppearance attribute, if TextView_textColor
   8710      * was not set directly.
   8711      */
   8712     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
   8713         ColorStateList colors;
   8714         colors = attrs.getColorStateList(com.android.internal.R.styleable.
   8715                                          TextView_textColor);
   8716 
   8717         if (colors == null) {
   8718             int ap = attrs.getResourceId(com.android.internal.R.styleable.
   8719                                          TextView_textAppearance, -1);
   8720             if (ap != -1) {
   8721                 TypedArray appearance;
   8722                 appearance = context.obtainStyledAttributes(ap,
   8723                                             com.android.internal.R.styleable.TextAppearance);
   8724                 colors = appearance.getColorStateList(com.android.internal.R.styleable.
   8725                                                   TextAppearance_textColor);
   8726                 appearance.recycle();
   8727             }
   8728         }
   8729 
   8730         return colors;
   8731     }
   8732 
   8733     /**
   8734      * Returns the default color from the TextView_textColor attribute
   8735      * from the AttributeSet, if set, or the default color from the
   8736      * TextAppearance_textColor from the TextView_textAppearance attribute,
   8737      * if TextView_textColor was not set directly.
   8738      */
   8739     public static int getTextColor(Context context,
   8740                                    TypedArray attrs,
   8741                                    int def) {
   8742         ColorStateList colors = getTextColors(context, attrs);
   8743 
   8744         if (colors == null) {
   8745             return def;
   8746         } else {
   8747             return colors.getDefaultColor();
   8748         }
   8749     }
   8750 
   8751     @Override
   8752     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
   8753         final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
   8754         if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
   8755             switch (keyCode) {
   8756             case KeyEvent.KEYCODE_A:
   8757                 if (canSelectText()) {
   8758                     return onTextContextMenuItem(ID_SELECT_ALL);
   8759                 }
   8760                 break;
   8761             case KeyEvent.KEYCODE_X:
   8762                 if (canCut()) {
   8763                     return onTextContextMenuItem(ID_CUT);
   8764                 }
   8765                 break;
   8766             case KeyEvent.KEYCODE_C:
   8767                 if (canCopy()) {
   8768                     return onTextContextMenuItem(ID_COPY);
   8769                 }
   8770                 break;
   8771             case KeyEvent.KEYCODE_V:
   8772                 if (canPaste()) {
   8773                     return onTextContextMenuItem(ID_PASTE);
   8774                 }
   8775                 break;
   8776             }
   8777         }
   8778         return super.onKeyShortcut(keyCode, event);
   8779     }
   8780 
   8781     /**
   8782      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
   8783      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
   8784      * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient.
   8785      */
   8786     private boolean canSelectText() {
   8787         return hasSelectionController() && mText.length() != 0;
   8788     }
   8789 
   8790     /**
   8791      * Test based on the <i>intrinsic</i> charateristics of the TextView.
   8792      * The text must be spannable and the movement method must allow for arbitary selection.
   8793      *
   8794      * See also {@link #canSelectText()}.
   8795      */
   8796     private boolean textCanBeSelected() {
   8797         // prepareCursorController() relies on this method.
   8798         // If you change this condition, make sure prepareCursorController is called anywhere
   8799         // the value of this condition might be changed.
   8800         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
   8801         return isTextEditable() || (mTextIsSelectable && mText instanceof Spannable && isEnabled());
   8802     }
   8803 
   8804     private boolean canCut() {
   8805         if (hasPasswordTransformationMethod()) {
   8806             return false;
   8807         }
   8808 
   8809         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
   8810             return true;
   8811         }
   8812 
   8813         return false;
   8814     }
   8815 
   8816     private boolean canCopy() {
   8817         if (hasPasswordTransformationMethod()) {
   8818             return false;
   8819         }
   8820 
   8821         if (mText.length() > 0 && hasSelection()) {
   8822             return true;
   8823         }
   8824 
   8825         return false;
   8826     }
   8827 
   8828     private boolean canPaste() {
   8829         return (mText instanceof Editable &&
   8830                 mInput != null &&
   8831                 getSelectionStart() >= 0 &&
   8832                 getSelectionEnd() >= 0 &&
   8833                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
   8834                 hasPrimaryClip());
   8835     }
   8836 
   8837     private static long packRangeInLong(int start, int end) {
   8838         return (((long) start) << 32) | end;
   8839     }
   8840 
   8841     private static int extractRangeStartFromLong(long range) {
   8842         return (int) (range >>> 32);
   8843     }
   8844 
   8845     private static int extractRangeEndFromLong(long range) {
   8846         return (int) (range & 0x00000000FFFFFFFFL);
   8847     }
   8848 
   8849     private boolean selectAll() {
   8850         final int length = mText.length();
   8851         Selection.setSelection((Spannable) mText, 0, length);
   8852         return length > 0;
   8853     }
   8854 
   8855     /**
   8856      * Adjusts selection to the word under last touch offset.
   8857      * Return true if the operation was successfully performed.
   8858      */
   8859     private boolean selectCurrentWord() {
   8860         if (!canSelectText()) {
   8861             return false;
   8862         }
   8863 
   8864         if (hasPasswordTransformationMethod()) {
   8865             // Always select all on a password field.
   8866             // Cut/copy menu entries are not available for passwords, but being able to select all
   8867             // is however useful to delete or paste to replace the entire content.
   8868             return selectAll();
   8869         }
   8870 
   8871         int klass = mInputType & InputType.TYPE_MASK_CLASS;
   8872         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
   8873 
   8874         // Specific text field types: select the entire text for these
   8875         if (klass == InputType.TYPE_CLASS_NUMBER ||
   8876                 klass == InputType.TYPE_CLASS_PHONE ||
   8877                 klass == InputType.TYPE_CLASS_DATETIME ||
   8878                 variation == InputType.TYPE_TEXT_VARIATION_URI ||
   8879                 variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
   8880                 variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
   8881                 variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
   8882             return selectAll();
   8883         }
   8884 
   8885         long lastTouchOffsets = getLastTouchOffsets();
   8886         final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
   8887         final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
   8888 
   8889         // Safety check in case standard touch event handling has been bypassed
   8890         if (minOffset < 0 || minOffset >= mText.length()) return false;
   8891         if (maxOffset < 0 || maxOffset >= mText.length()) return false;
   8892 
   8893         int selectionStart, selectionEnd;
   8894 
   8895         // If a URLSpan (web address, email, phone...) is found at that position, select it.
   8896         URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
   8897         if (urlSpans.length >= 1) {
   8898             URLSpan urlSpan = urlSpans[0];
   8899             selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
   8900             selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
   8901         } else {
   8902             final WordIterator wordIterator = getWordIterator();
   8903             wordIterator.setCharSequence(mText, minOffset, maxOffset);
   8904 
   8905             selectionStart = wordIterator.getBeginning(minOffset);
   8906             if (selectionStart == BreakIterator.DONE) return false;
   8907 
   8908             selectionEnd = wordIterator.getEnd(maxOffset);
   8909             if (selectionEnd == BreakIterator.DONE) return false;
   8910 
   8911             if (selectionStart == selectionEnd) {
   8912                 // Possible when the word iterator does not properly handle the text's language
   8913                 long range = getCharRange(selectionStart);
   8914                 selectionStart = extractRangeStartFromLong(range);
   8915                 selectionEnd = extractRangeEndFromLong(range);
   8916             }
   8917         }
   8918 
   8919         Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
   8920         return selectionEnd > selectionStart;
   8921     }
   8922 
   8923     /**
   8924      * This is a temporary method. Future versions may support multi-locale text.
   8925      *
   8926      * @return The locale that should be used for a word iterator and a spell checker
   8927      * in this TextView, based on the current spell checker settings,
   8928      * the current IME's locale, or the system default locale.
   8929      * @hide
   8930      */
   8931     public Locale getTextServicesLocale() {
   8932         Locale locale = Locale.getDefault();
   8933         final TextServicesManager textServicesManager = (TextServicesManager)
   8934                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
   8935         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
   8936         if (subtype != null) {
   8937             locale = new Locale(subtype.getLocale());
   8938         }
   8939         return locale;
   8940     }
   8941 
   8942     void onLocaleChanged() {
   8943         // Will be re-created on demand in getWordIterator with the proper new locale
   8944         mWordIterator = null;
   8945     }
   8946 
   8947     /**
   8948      * @hide
   8949      */
   8950     public WordIterator getWordIterator() {
   8951         if (mWordIterator == null) {
   8952             mWordIterator = new WordIterator(getTextServicesLocale());
   8953         }
   8954         return mWordIterator;
   8955     }
   8956 
   8957     private long getCharRange(int offset) {
   8958         final int textLength = mText.length();
   8959         if (offset + 1 < textLength) {
   8960             final char currentChar = mText.charAt(offset);
   8961             final char nextChar = mText.charAt(offset + 1);
   8962             if (Character.isSurrogatePair(currentChar, nextChar)) {
   8963                 return packRangeInLong(offset,  offset + 2);
   8964             }
   8965         }
   8966         if (offset < textLength) {
   8967             return packRangeInLong(offset,  offset + 1);
   8968         }
   8969         if (offset - 2 >= 0) {
   8970             final char previousChar = mText.charAt(offset - 1);
   8971             final char previousPreviousChar = mText.charAt(offset - 2);
   8972             if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
   8973                 return packRangeInLong(offset - 2,  offset);
   8974             }
   8975         }
   8976         if (offset - 1 >= 0) {
   8977             return packRangeInLong(offset - 1,  offset);
   8978         }
   8979         return packRangeInLong(offset,  offset);
   8980     }
   8981 
   8982     private long getLastTouchOffsets() {
   8983         SelectionModifierCursorController selectionController = getSelectionController();
   8984         final int minOffset = selectionController.getMinTouchOffset();
   8985         final int maxOffset = selectionController.getMaxTouchOffset();
   8986         return packRangeInLong(minOffset, maxOffset);
   8987     }
   8988 
   8989     @Override
   8990     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
   8991         super.onPopulateAccessibilityEvent(event);
   8992 
   8993         final boolean isPassword = hasPasswordTransformationMethod();
   8994         if (!isPassword) {
   8995             CharSequence text = getTextForAccessibility();
   8996             if (!TextUtils.isEmpty(text)) {
   8997                 event.getText().add(text);
   8998             }
   8999         }
   9000     }
   9001 
   9002     @Override
   9003     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   9004         super.onInitializeAccessibilityEvent(event);
   9005 
   9006         final boolean isPassword = hasPasswordTransformationMethod();
   9007         event.setPassword(isPassword);
   9008 
   9009         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
   9010             event.setFromIndex(Selection.getSelectionStart(mText));
   9011             event.setToIndex(Selection.getSelectionEnd(mText));
   9012             event.setItemCount(mText.length());
   9013         }
   9014     }
   9015 
   9016     @Override
   9017     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   9018         super.onInitializeAccessibilityNodeInfo(info);
   9019 
   9020         final boolean isPassword = hasPasswordTransformationMethod();
   9021         if (!isPassword) {
   9022             info.setText(getTextForAccessibility());
   9023         }
   9024         info.setPassword(isPassword);
   9025     }
   9026 
   9027     @Override
   9028     public void sendAccessibilityEvent(int eventType) {
   9029         // Do not send scroll events since first they are not interesting for
   9030         // accessibility and second such events a generated too frequently.
   9031         // For details see the implementation of bringTextIntoView().
   9032         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
   9033             return;
   9034         }
   9035         super.sendAccessibilityEvent(eventType);
   9036     }
   9037 
   9038     /**
   9039      * Gets the text reported for accessibility purposes. It is the
   9040      * text if not empty or the hint.
   9041      *
   9042      * @return The accessibility text.
   9043      */
   9044     private CharSequence getTextForAccessibility() {
   9045         CharSequence text = getText();
   9046         if (TextUtils.isEmpty(text)) {
   9047             text = getHint();
   9048         }
   9049         return text;
   9050     }
   9051 
   9052     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
   9053             int fromIndex, int removedCount, int addedCount) {
   9054         AccessibilityEvent event =
   9055             AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
   9056         event.setFromIndex(fromIndex);
   9057         event.setRemovedCount(removedCount);
   9058         event.setAddedCount(addedCount);
   9059         event.setBeforeText(beforeText);
   9060         sendAccessibilityEventUnchecked(event);
   9061     }
   9062 
   9063     /**
   9064      * Returns whether this text view is a current input method target.  The
   9065      * default implementation just checks with {@link InputMethodManager}.
   9066      */
   9067     public boolean isInputMethodTarget() {
   9068         InputMethodManager imm = InputMethodManager.peekInstance();
   9069         return imm != null && imm.isActive(this);
   9070     }
   9071 
   9072     // Selection context mode
   9073     private static final int ID_SELECT_ALL = android.R.id.selectAll;
   9074     private static final int ID_CUT = android.R.id.cut;
   9075     private static final int ID_COPY = android.R.id.copy;
   9076     private static final int ID_PASTE = android.R.id.paste;
   9077 
   9078     /**
   9079      * Called when a context menu option for the text view is selected.  Currently
   9080      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
   9081      * {@link android.R.id#copy} or {@link android.R.id#paste}.
   9082      *
   9083      * @return true if the context menu item action was performed.
   9084      */
   9085     public boolean onTextContextMenuItem(int id) {
   9086         int min = 0;
   9087         int max = mText.length();
   9088 
   9089         if (isFocused()) {
   9090             final int selStart = getSelectionStart();
   9091             final int selEnd = getSelectionEnd();
   9092 
   9093             min = Math.max(0, Math.min(selStart, selEnd));
   9094             max = Math.max(0, Math.max(selStart, selEnd));
   9095         }
   9096 
   9097         switch (id) {
   9098             case ID_SELECT_ALL:
   9099                 // This does not enter text selection mode. Text is highlighted, so that it can be
   9100                 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
   9101                 selectAll();
   9102                 return true;
   9103 
   9104             case ID_PASTE:
   9105                 paste(min, max);
   9106                 return true;
   9107 
   9108             case ID_CUT:
   9109                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
   9110                 deleteText_internal(min, max);
   9111                 stopSelectionActionMode();
   9112                 return true;
   9113 
   9114             case ID_COPY:
   9115                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
   9116                 stopSelectionActionMode();
   9117                 return true;
   9118         }
   9119         return false;
   9120     }
   9121 
   9122     private CharSequence getTransformedText(int start, int end) {
   9123         return removeSuggestionSpans(mTransformed.subSequence(start, end));
   9124     }
   9125 
   9126     /**
   9127      * Prepare text so that there are not zero or two spaces at beginning and end of region defined
   9128      * by [min, max] when replacing this region by paste.
   9129      * Note that if there were two spaces (or more) at that position before, they are kept. We just
   9130      * make sure we do not add an extra one from the paste content.
   9131      */
   9132     private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
   9133         if (paste.length() > 0) {
   9134             if (min > 0) {
   9135                 final char charBefore = mTransformed.charAt(min - 1);
   9136                 final char charAfter = paste.charAt(0);
   9137 
   9138                 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
   9139                     // Two spaces at beginning of paste: remove one
   9140                     final int originalLength = mText.length();
   9141                     deleteText_internal(min - 1, min);
   9142                     // Due to filters, there is no guarantee that exactly one character was
   9143                     // removed: count instead.
   9144                     final int delta = mText.length() - originalLength;
   9145                     min += delta;
   9146                     max += delta;
   9147                 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
   9148                         !Character.isSpaceChar(charAfter) && charAfter != '\n') {
   9149                     // No space at beginning of paste: add one
   9150                     final int originalLength = mText.length();
   9151                     replaceText_internal(min, min, " ");
   9152                     // Taking possible filters into account as above.
   9153                     final int delta = mText.length() - originalLength;
   9154                     min += delta;
   9155                     max += delta;
   9156                 }
   9157             }
   9158 
   9159             if (max < mText.length()) {
   9160                 final char charBefore = paste.charAt(paste.length() - 1);
   9161                 final char charAfter = mTransformed.charAt(max);
   9162 
   9163                 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
   9164                     // Two spaces at end of paste: remove one
   9165                     deleteText_internal(max, max + 1);
   9166                 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
   9167                         !Character.isSpaceChar(charAfter) && charAfter != '\n') {
   9168                     // No space at end of paste: add one
   9169                     replaceText_internal(max, max, " ");
   9170                 }
   9171             }
   9172         }
   9173 
   9174         return packRangeInLong(min, max);
   9175     }
   9176 
   9177     private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
   9178         TextView shadowView = (TextView) inflate(mContext,
   9179                 com.android.internal.R.layout.text_drag_thumbnail, null);
   9180 
   9181         if (shadowView == null) {
   9182             throw new IllegalArgumentException("Unable to inflate text drag thumbnail");
   9183         }
   9184 
   9185         if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) {
   9186             text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH);
   9187         }
   9188         shadowView.setText(text);
   9189         shadowView.setTextColor(getTextColors());
   9190 
   9191         shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge);
   9192         shadowView.setGravity(Gravity.CENTER);
   9193 
   9194         shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   9195                 ViewGroup.LayoutParams.WRAP_CONTENT));
   9196 
   9197         final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
   9198         shadowView.measure(size, size);
   9199 
   9200         shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight());
   9201         shadowView.invalidate();
   9202         return new DragShadowBuilder(shadowView);
   9203     }
   9204 
   9205     private static class DragLocalState {
   9206         public TextView sourceTextView;
   9207         public int start, end;
   9208 
   9209         public DragLocalState(TextView sourceTextView, int start, int end) {
   9210             this.sourceTextView = sourceTextView;
   9211             this.start = start;
   9212             this.end = end;
   9213         }
   9214     }
   9215 
   9216     @Override
   9217     public boolean performLongClick() {
   9218         boolean handled = false;
   9219         boolean vibrate = true;
   9220 
   9221         if (super.performLongClick()) {
   9222             mDiscardNextActionUp = true;
   9223             handled = true;
   9224         }
   9225 
   9226         // Long press in empty space moves cursor and shows the Paste affordance if available.
   9227         if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
   9228                 mInsertionControllerEnabled) {
   9229             final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
   9230             stopSelectionActionMode();
   9231             Selection.setSelection((Spannable) mText, offset);
   9232             getInsertionController().showWithActionPopup();
   9233             handled = true;
   9234             vibrate = false;
   9235         }
   9236 
   9237         if (!handled && mSelectionActionMode != null) {
   9238             if (touchPositionIsInSelection()) {
   9239                 // Start a drag
   9240                 final int start = getSelectionStart();
   9241                 final int end = getSelectionEnd();
   9242                 CharSequence selectedText = getTransformedText(start, end);
   9243                 ClipData data = ClipData.newPlainText(null, selectedText);
   9244                 DragLocalState localState = new DragLocalState(this, start, end);
   9245                 startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
   9246                 stopSelectionActionMode();
   9247             } else {
   9248                 getSelectionController().hide();
   9249                 selectCurrentWord();
   9250                 getSelectionController().show();
   9251             }
   9252             handled = true;
   9253         }
   9254 
   9255         // Start a new selection
   9256         if (!handled) {
   9257             vibrate = handled = startSelectionActionMode();
   9258         }
   9259 
   9260         if (vibrate) {
   9261             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   9262         }
   9263 
   9264         if (handled) {
   9265             mDiscardNextActionUp = true;
   9266         }
   9267 
   9268         return handled;
   9269     }
   9270 
   9271     private boolean touchPositionIsInSelection() {
   9272         int selectionStart = getSelectionStart();
   9273         int selectionEnd = getSelectionEnd();
   9274 
   9275         if (selectionStart == selectionEnd) {
   9276             return false;
   9277         }
   9278 
   9279         if (selectionStart > selectionEnd) {
   9280             int tmp = selectionStart;
   9281             selectionStart = selectionEnd;
   9282             selectionEnd = tmp;
   9283             Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
   9284         }
   9285 
   9286         SelectionModifierCursorController selectionController = getSelectionController();
   9287         int minOffset = selectionController.getMinTouchOffset();
   9288         int maxOffset = selectionController.getMaxTouchOffset();
   9289 
   9290         return ((minOffset >= selectionStart) && (maxOffset < selectionEnd));
   9291     }
   9292 
   9293     private PositionListener getPositionListener() {
   9294         if (mPositionListener == null) {
   9295             mPositionListener = new PositionListener();
   9296         }
   9297         return mPositionListener;
   9298     }
   9299 
   9300     private interface TextViewPositionListener {
   9301         public void updatePosition(int parentPositionX, int parentPositionY,
   9302                 boolean parentPositionChanged, boolean parentScrolled);
   9303     }
   9304 
   9305     private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
   9306         // 3 handles
   9307         // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
   9308         private final int MAXIMUM_NUMBER_OF_LISTENERS = 6;
   9309         private TextViewPositionListener[] mPositionListeners =
   9310                 new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS];
   9311         private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS];
   9312         private boolean mPositionHasChanged = true;
   9313         // Absolute position of the TextView with respect to its parent window
   9314         private int mPositionX, mPositionY;
   9315         private int mNumberOfListeners;
   9316         private boolean mScrollHasChanged;
   9317 
   9318         public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
   9319             if (mNumberOfListeners == 0) {
   9320                 updatePosition();
   9321                 ViewTreeObserver vto = TextView.this.getViewTreeObserver();
   9322                 vto.addOnPreDrawListener(this);
   9323             }
   9324 
   9325             int emptySlotIndex = -1;
   9326             for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
   9327                 TextViewPositionListener listener = mPositionListeners[i];
   9328                 if (listener == positionListener) {
   9329                     return;
   9330                 } else if (emptySlotIndex < 0 && listener == null) {
   9331                     emptySlotIndex = i;
   9332                 }
   9333             }
   9334 
   9335             mPositionListeners[emptySlotIndex] = positionListener;
   9336             mCanMove[emptySlotIndex] = canMove;
   9337             mNumberOfListeners++;
   9338         }
   9339 
   9340         public void removeSubscriber(TextViewPositionListener positionListener) {
   9341             for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
   9342                 if (mPositionListeners[i] == positionListener) {
   9343                     mPositionListeners[i] = null;
   9344                     mNumberOfListeners--;
   9345                     break;
   9346                 }
   9347             }
   9348 
   9349             if (mNumberOfListeners == 0) {
   9350                 ViewTreeObserver vto = TextView.this.getViewTreeObserver();
   9351                 vto.removeOnPreDrawListener(this);
   9352             }
   9353         }
   9354 
   9355         public int getPositionX() {
   9356             return mPositionX;
   9357         }
   9358 
   9359         public int getPositionY() {
   9360             return mPositionY;
   9361         }
   9362 
   9363         @Override
   9364         public boolean onPreDraw() {
   9365             updatePosition();
   9366 
   9367             for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) {
   9368                 if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) {
   9369                     TextViewPositionListener positionListener = mPositionListeners[i];
   9370                     if (positionListener != null) {
   9371                         positionListener.updatePosition(mPositionX, mPositionY,
   9372                                 mPositionHasChanged, mScrollHasChanged);
   9373                     }
   9374                 }
   9375             }
   9376 
   9377             mScrollHasChanged = false;
   9378             return true;
   9379         }
   9380 
   9381         private void updatePosition() {
   9382             TextView.this.getLocationInWindow(mTempCoords);
   9383 
   9384             mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY;
   9385 
   9386             mPositionX = mTempCoords[0];
   9387             mPositionY = mTempCoords[1];
   9388         }
   9389 
   9390         public void onScrollChanged() {
   9391             mScrollHasChanged = true;
   9392         }
   9393     }
   9394 
   9395     private boolean isPositionVisible(int positionX, int positionY) {
   9396         synchronized (sTmpPosition) {
   9397             final float[] position = sTmpPosition;
   9398             position[0] = positionX;
   9399             position[1] = positionY;
   9400             View view = this;
   9401 
   9402             while (view != null) {
   9403                 if (view != this) {
   9404                     // Local scroll is already taken into account in positionX/Y
   9405                     position[0] -= view.getScrollX();
   9406                     position[1] -= view.getScrollY();
   9407                 }
   9408 
   9409                 if (position[0] < 0 || position[1] < 0 ||
   9410                         position[0] > view.getWidth() || position[1] > view.getHeight()) {
   9411                     return false;
   9412                 }
   9413 
   9414                 if (!view.getMatrix().isIdentity()) {
   9415                     view.getMatrix().mapPoints(position);
   9416                 }
   9417 
   9418                 position[0] += view.getLeft();
   9419                 position[1] += view.getTop();
   9420 
   9421                 final ViewParent parent = view.getParent();
   9422                 if (parent instanceof View) {
   9423                     view = (View) parent;
   9424                 } else {
   9425                     // We've reached the ViewRoot, stop iterating
   9426                     view = null;
   9427                 }
   9428             }
   9429         }
   9430 
   9431         // We've been able to walk up the view hierarchy and the position was never clipped
   9432         return true;
   9433     }
   9434 
   9435     private boolean isOffsetVisible(int offset) {
   9436         final int line = mLayout.getLineForOffset(offset);
   9437         final int lineBottom = mLayout.getLineBottom(line);
   9438         final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
   9439         return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
   9440                 lineBottom + viewportToContentVerticalOffset());
   9441     }
   9442 
   9443     @Override
   9444     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
   9445         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
   9446         if (mPositionListener != null) {
   9447             mPositionListener.onScrollChanged();
   9448         }
   9449     }
   9450 
   9451     private abstract class PinnedPopupWindow implements TextViewPositionListener {
   9452         protected PopupWindow mPopupWindow;
   9453         protected ViewGroup mContentView;
   9454         int mPositionX, mPositionY;
   9455 
   9456         protected abstract void createPopupWindow();
   9457         protected abstract void initContentView();
   9458         protected abstract int getTextOffset();
   9459         protected abstract int getVerticalLocalPosition(int line);
   9460         protected abstract int clipVertically(int positionY);
   9461 
   9462         public PinnedPopupWindow() {
   9463             createPopupWindow();
   9464 
   9465             mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
   9466             mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
   9467             mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
   9468 
   9469             initContentView();
   9470 
   9471             LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   9472                     ViewGroup.LayoutParams.WRAP_CONTENT);
   9473             mContentView.setLayoutParams(wrapContent);
   9474 
   9475             mPopupWindow.setContentView(mContentView);
   9476         }
   9477 
   9478         public void show() {
   9479             TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */);
   9480 
   9481             computeLocalPosition();
   9482 
   9483             final PositionListener positionListener = TextView.this.getPositionListener();
   9484             updatePosition(positionListener.getPositionX(), positionListener.getPositionY());
   9485         }
   9486 
   9487         protected void measureContent() {
   9488             final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
   9489             mContentView.measure(
   9490                     View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
   9491                             View.MeasureSpec.AT_MOST),
   9492                     View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
   9493                             View.MeasureSpec.AT_MOST));
   9494         }
   9495 
   9496         /* The popup window will be horizontally centered on the getTextOffset() and vertically
   9497          * positioned according to viewportToContentHorizontalOffset.
   9498          *
   9499          * This method assumes that mContentView has properly been measured from its content. */
   9500         private void computeLocalPosition() {
   9501             measureContent();
   9502             final int width = mContentView.getMeasuredWidth();
   9503             final int offset = getTextOffset();
   9504             mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f);
   9505             mPositionX += viewportToContentHorizontalOffset();
   9506 
   9507             final int line = mLayout.getLineForOffset(offset);
   9508             mPositionY = getVerticalLocalPosition(line);
   9509             mPositionY += viewportToContentVerticalOffset();
   9510         }
   9511 
   9512         private void updatePosition(int parentPositionX, int parentPositionY) {
   9513             int positionX = parentPositionX + mPositionX;
   9514             int positionY = parentPositionY + mPositionY;
   9515 
   9516             positionY = clipVertically(positionY);
   9517 
   9518             // Horizontal clipping
   9519             final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
   9520             final int width = mContentView.getMeasuredWidth();
   9521             positionX = Math.min(displayMetrics.widthPixels - width, positionX);
   9522             positionX = Math.max(0, positionX);
   9523 
   9524             if (isShowing()) {
   9525                 mPopupWindow.update(positionX, positionY, -1, -1);
   9526             } else {
   9527                 mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
   9528                         positionX, positionY);
   9529             }
   9530         }
   9531 
   9532         public void hide() {
   9533             mPopupWindow.dismiss();
   9534             TextView.this.getPositionListener().removeSubscriber(this);
   9535         }
   9536 
   9537         @Override
   9538         public void updatePosition(int parentPositionX, int parentPositionY,
   9539                 boolean parentPositionChanged, boolean parentScrolled) {
   9540             // Either parentPositionChanged or parentScrolled is true, check if still visible
   9541             if (isShowing() && isOffsetVisible(getTextOffset())) {
   9542                 if (parentScrolled) computeLocalPosition();
   9543                 updatePosition(parentPositionX, parentPositionY);
   9544             } else {
   9545                 hide();
   9546             }
   9547         }
   9548 
   9549         public boolean isShowing() {
   9550             return mPopupWindow.isShowing();
   9551         }
   9552     }
   9553 
   9554     private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
   9555         private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
   9556         private static final int ADD_TO_DICTIONARY = -1;
   9557         private static final int DELETE_TEXT = -2;
   9558         private SuggestionInfo[] mSuggestionInfos;
   9559         private int mNumberOfSuggestions;
   9560         private boolean mCursorWasVisibleBeforeSuggestions;
   9561         private boolean mIsShowingUp = false;
   9562         private SuggestionAdapter mSuggestionsAdapter;
   9563         private final Comparator<SuggestionSpan> mSuggestionSpanComparator;
   9564         private final HashMap<SuggestionSpan, Integer> mSpansLengths;
   9565 
   9566         private class CustomPopupWindow extends PopupWindow {
   9567             public CustomPopupWindow(Context context, int defStyle) {
   9568                 super(context, null, defStyle);
   9569             }
   9570 
   9571             @Override
   9572             public void dismiss() {
   9573                 super.dismiss();
   9574 
   9575                 TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
   9576 
   9577                 // Safe cast since show() checks that mText is an Editable
   9578                 ((Spannable) mText).removeSpan(mSuggestionRangeSpan);
   9579 
   9580                 setCursorVisible(mCursorWasVisibleBeforeSuggestions);
   9581                 if (hasInsertionController()) {
   9582                     getInsertionController().show();
   9583                 }
   9584             }
   9585         }
   9586 
   9587         public SuggestionsPopupWindow() {
   9588             mCursorWasVisibleBeforeSuggestions = mCursorVisible;
   9589             mSuggestionSpanComparator = new SuggestionSpanComparator();
   9590             mSpansLengths = new HashMap<SuggestionSpan, Integer>();
   9591         }
   9592 
   9593         @Override
   9594         protected void createPopupWindow() {
   9595             mPopupWindow = new CustomPopupWindow(TextView.this.mContext,
   9596                 com.android.internal.R.attr.textSuggestionsWindowStyle);
   9597             mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
   9598             mPopupWindow.setFocusable(true);
   9599             mPopupWindow.setClippingEnabled(false);
   9600         }
   9601 
   9602         @Override
   9603         protected void initContentView() {
   9604             ListView listView = new ListView(TextView.this.getContext());
   9605             mSuggestionsAdapter = new SuggestionAdapter();
   9606             listView.setAdapter(mSuggestionsAdapter);
   9607             listView.setOnItemClickListener(this);
   9608             mContentView = listView;
   9609 
   9610             // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete
   9611             mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2];
   9612             for (int i = 0; i < mSuggestionInfos.length; i++) {
   9613                 mSuggestionInfos[i] = new SuggestionInfo();
   9614             }
   9615         }
   9616 
   9617         public boolean isShowingUp() {
   9618             return mIsShowingUp;
   9619         }
   9620 
   9621         public void onParentLostFocus() {
   9622             mIsShowingUp = false;
   9623         }
   9624 
   9625         private class SuggestionInfo {
   9626             int suggestionStart, suggestionEnd; // range of actual suggestion within text
   9627             SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
   9628             int suggestionIndex; // the index of this suggestion inside suggestionSpan
   9629             SpannableStringBuilder text = new SpannableStringBuilder();
   9630             TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
   9631                     android.R.style.TextAppearance_SuggestionHighlight);
   9632         }
   9633 
   9634         private class SuggestionAdapter extends BaseAdapter {
   9635             private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext.
   9636                     getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   9637 
   9638             @Override
   9639             public int getCount() {
   9640                 return mNumberOfSuggestions;
   9641             }
   9642 
   9643             @Override
   9644             public Object getItem(int position) {
   9645                 return mSuggestionInfos[position];
   9646             }
   9647 
   9648             @Override
   9649             public long getItemId(int position) {
   9650                 return position;
   9651             }
   9652 
   9653             @Override
   9654             public View getView(int position, View convertView, ViewGroup parent) {
   9655                 TextView textView = (TextView) convertView;
   9656 
   9657                 if (textView == null) {
   9658                     textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent,
   9659                             false);
   9660                 }
   9661 
   9662                 final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
   9663                 textView.setText(suggestionInfo.text);
   9664 
   9665                 if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
   9666                     textView.setCompoundDrawablesWithIntrinsicBounds(
   9667                             com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0);
   9668                 } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
   9669                     textView.setCompoundDrawablesWithIntrinsicBounds(
   9670                             com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0);
   9671                 } else {
   9672                     textView.setCompoundDrawables(null, null, null, null);
   9673                 }
   9674 
   9675                 return textView;
   9676             }
   9677         }
   9678 
   9679         private class SuggestionSpanComparator implements Comparator<SuggestionSpan> {
   9680             public int compare(SuggestionSpan span1, SuggestionSpan span2) {
   9681                 final int flag1 = span1.getFlags();
   9682                 final int flag2 = span2.getFlags();
   9683                 if (flag1 != flag2) {
   9684                     // The order here should match what is used in updateDrawState
   9685                     final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
   9686                     final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
   9687                     final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0;
   9688                     final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0;
   9689                     if (easy1 && !misspelled1) return -1;
   9690                     if (easy2 && !misspelled2) return 1;
   9691                     if (misspelled1) return -1;
   9692                     if (misspelled2) return 1;
   9693                 }
   9694 
   9695                 return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue();
   9696             }
   9697         }
   9698 
   9699         /**
   9700          * Returns the suggestion spans that cover the current cursor position. The suggestion
   9701          * spans are sorted according to the length of text that they are attached to.
   9702          */
   9703         private SuggestionSpan[] getSuggestionSpans() {
   9704             int pos = TextView.this.getSelectionStart();
   9705             Spannable spannable = (Spannable) TextView.this.mText;
   9706             SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class);
   9707 
   9708             mSpansLengths.clear();
   9709             for (SuggestionSpan suggestionSpan : suggestionSpans) {
   9710                 int start = spannable.getSpanStart(suggestionSpan);
   9711                 int end = spannable.getSpanEnd(suggestionSpan);
   9712                 mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start));
   9713             }
   9714 
   9715             // The suggestions are sorted according to their types (easy correction first, then
   9716             // misspelled) and to the length of the text that they cover (shorter first).
   9717             Arrays.sort(suggestionSpans, mSuggestionSpanComparator);
   9718             return suggestionSpans;
   9719         }
   9720 
   9721         @Override
   9722         public void show() {
   9723             if (!(mText instanceof Editable)) return;
   9724 
   9725             updateSuggestions();
   9726             mCursorWasVisibleBeforeSuggestions = mCursorVisible;
   9727             setCursorVisible(false);
   9728             mIsShowingUp = true;
   9729             super.show();
   9730         }
   9731 
   9732         @Override
   9733         protected void measureContent() {
   9734             final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
   9735             final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec(
   9736                     displayMetrics.widthPixels, View.MeasureSpec.AT_MOST);
   9737             final int verticalMeasure = View.MeasureSpec.makeMeasureSpec(
   9738                     displayMetrics.heightPixels, View.MeasureSpec.AT_MOST);
   9739 
   9740             int width = 0;
   9741             View view = null;
   9742             for (int i = 0; i < mNumberOfSuggestions; i++) {
   9743                 view = mSuggestionsAdapter.getView(i, view, mContentView);
   9744                 view.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
   9745                 view.measure(horizontalMeasure, verticalMeasure);
   9746                 width = Math.max(width, view.getMeasuredWidth());
   9747             }
   9748 
   9749             // Enforce the width based on actual text widths
   9750             mContentView.measure(
   9751                     View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
   9752                     verticalMeasure);
   9753 
   9754             Drawable popupBackground = mPopupWindow.getBackground();
   9755             if (popupBackground != null) {
   9756                 if (mTempRect == null) mTempRect = new Rect();
   9757                 popupBackground.getPadding(mTempRect);
   9758                 width += mTempRect.left + mTempRect.right;
   9759             }
   9760             mPopupWindow.setWidth(width);
   9761         }
   9762 
   9763         @Override
   9764         protected int getTextOffset() {
   9765             return getSelectionStart();
   9766         }
   9767 
   9768         @Override
   9769         protected int getVerticalLocalPosition(int line) {
   9770             return mLayout.getLineBottom(line);
   9771         }
   9772 
   9773         @Override
   9774         protected int clipVertically(int positionY) {
   9775             final int height = mContentView.getMeasuredHeight();
   9776             final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
   9777             return Math.min(positionY, displayMetrics.heightPixels - height);
   9778         }
   9779 
   9780         @Override
   9781         public void hide() {
   9782             super.hide();
   9783         }
   9784 
   9785         private void updateSuggestions() {
   9786             Spannable spannable = (Spannable) TextView.this.mText;
   9787             SuggestionSpan[] suggestionSpans = getSuggestionSpans();
   9788 
   9789             final int nbSpans = suggestionSpans.length;
   9790 
   9791             mNumberOfSuggestions = 0;
   9792             int spanUnionStart = mText.length();
   9793             int spanUnionEnd = 0;
   9794 
   9795             SuggestionSpan misspelledSpan = null;
   9796             int underlineColor = 0;
   9797 
   9798             for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
   9799                 SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
   9800                 final int spanStart = spannable.getSpanStart(suggestionSpan);
   9801                 final int spanEnd = spannable.getSpanEnd(suggestionSpan);
   9802                 spanUnionStart = Math.min(spanStart, spanUnionStart);
   9803                 spanUnionEnd = Math.max(spanEnd, spanUnionEnd);
   9804 
   9805                 if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
   9806                     misspelledSpan = suggestionSpan;
   9807                 }
   9808 
   9809                 // The first span dictates the background color of the highlighted text
   9810                 if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor();
   9811 
   9812                 String[] suggestions = suggestionSpan.getSuggestions();
   9813                 int nbSuggestions = suggestions.length;
   9814                 for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
   9815                     SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
   9816                     suggestionInfo.suggestionSpan = suggestionSpan;
   9817                     suggestionInfo.suggestionIndex = suggestionIndex;
   9818                     suggestionInfo.text.replace(0, suggestionInfo.text.length(),
   9819                             suggestions[suggestionIndex]);
   9820 
   9821                     mNumberOfSuggestions++;
   9822                     if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
   9823                         // Also end outer for loop
   9824                         spanIndex = nbSpans;
   9825                         break;
   9826                     }
   9827                 }
   9828             }
   9829 
   9830             for (int i = 0; i < mNumberOfSuggestions; i++) {
   9831                 highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
   9832             }
   9833 
   9834             // Add to dictionary item if there is a span with the misspelled flag
   9835             if (misspelledSpan != null) {
   9836                 final int misspelledStart = spannable.getSpanStart(misspelledSpan);
   9837                 final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
   9838                 if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
   9839                     SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
   9840                     suggestionInfo.suggestionSpan = misspelledSpan;
   9841                     suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY;
   9842                     suggestionInfo.text.replace(0, suggestionInfo.text.length(),
   9843                             getContext().getString(com.android.internal.R.string.addToDictionary));
   9844                     suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
   9845                             Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   9846 
   9847                     mNumberOfSuggestions++;
   9848                 }
   9849             }
   9850 
   9851             // Delete item
   9852             SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
   9853             suggestionInfo.suggestionSpan = null;
   9854             suggestionInfo.suggestionIndex = DELETE_TEXT;
   9855             suggestionInfo.text.replace(0, suggestionInfo.text.length(),
   9856                     getContext().getString(com.android.internal.R.string.deleteText));
   9857             suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0,
   9858                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   9859             mNumberOfSuggestions++;
   9860 
   9861             if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
   9862             if (underlineColor == 0) {
   9863                 // Fallback on the default highlight color when the first span does not provide one
   9864                 mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
   9865             } else {
   9866                 final float BACKGROUND_TRANSPARENCY = 0.4f;
   9867                 final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
   9868                 mSuggestionRangeSpan.setBackgroundColor(
   9869                         (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
   9870             }
   9871             spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
   9872                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   9873 
   9874             mSuggestionsAdapter.notifyDataSetChanged();
   9875         }
   9876 
   9877         private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
   9878                 int unionEnd) {
   9879             final Spannable text = (Spannable) mText;
   9880             final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan);
   9881             final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan);
   9882 
   9883             // Adjust the start/end of the suggestion span
   9884             suggestionInfo.suggestionStart = spanStart - unionStart;
   9885             suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart
   9886                     + suggestionInfo.text.length();
   9887 
   9888             suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0,
   9889                     suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   9890 
   9891             // Add the text before and after the span.
   9892             suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart));
   9893             suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd));
   9894         }
   9895 
   9896         @Override
   9897         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   9898             Editable editable = (Editable) mText;
   9899             SuggestionInfo suggestionInfo = mSuggestionInfos[position];
   9900 
   9901             if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
   9902                 final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan);
   9903                 int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan);
   9904                 if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
   9905                     // Do not leave two adjacent spaces after deletion, or one at beginning of text
   9906                     if (spanUnionEnd < editable.length() &&
   9907                             Character.isSpaceChar(editable.charAt(spanUnionEnd)) &&
   9908                             (spanUnionStart == 0 ||
   9909                             Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) {
   9910                         spanUnionEnd = spanUnionEnd + 1;
   9911                     }
   9912                     deleteText_internal(spanUnionStart, spanUnionEnd);
   9913                 }
   9914                 hide();
   9915                 return;
   9916             }
   9917 
   9918             final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan);
   9919             final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan);
   9920             if (spanStart < 0 || spanEnd <= spanStart) {
   9921                 // Span has been removed
   9922                 hide();
   9923                 return;
   9924             }
   9925             final String originalText = mText.toString().substring(spanStart, spanEnd);
   9926 
   9927             if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
   9928                 Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
   9929                 intent.putExtra("word", originalText);
   9930                 intent.putExtra("locale", getTextServicesLocale().toString());
   9931                 intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
   9932                 getContext().startActivity(intent);
   9933                 // There is no way to know if the word was indeed added. Re-check.
   9934                 // TODO The ExtractEditText should remove the span in the original text instead
   9935                 editable.removeSpan(suggestionInfo.suggestionSpan);
   9936                 updateSpellCheckSpans(spanStart, spanEnd, false);
   9937             } else {
   9938                 // SuggestionSpans are removed by replace: save them before
   9939                 SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
   9940                         SuggestionSpan.class);
   9941                 final int length = suggestionSpans.length;
   9942                 int[] suggestionSpansStarts = new int[length];
   9943                 int[] suggestionSpansEnds = new int[length];
   9944                 int[] suggestionSpansFlags = new int[length];
   9945                 for (int i = 0; i < length; i++) {
   9946                     final SuggestionSpan suggestionSpan = suggestionSpans[i];
   9947                     suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
   9948                     suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
   9949                     suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
   9950 
   9951                     // Remove potential misspelled flags
   9952                     int suggestionSpanFlags = suggestionSpan.getFlags();
   9953                     if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
   9954                         suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
   9955                         suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
   9956                         suggestionSpan.setFlags(suggestionSpanFlags);
   9957                     }
   9958                 }
   9959 
   9960                 final int suggestionStart = suggestionInfo.suggestionStart;
   9961                 final int suggestionEnd = suggestionInfo.suggestionEnd;
   9962                 final String suggestion = suggestionInfo.text.subSequence(
   9963                         suggestionStart, suggestionEnd).toString();
   9964                 replaceText_internal(spanStart, spanEnd, suggestion);
   9965 
   9966                 // Notify source IME of the suggestion pick. Do this before swaping texts.
   9967                 if (!TextUtils.isEmpty(
   9968                         suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
   9969                     InputMethodManager imm = InputMethodManager.peekInstance();
   9970                     if (imm != null) {
   9971                         imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
   9972                                 suggestionInfo.suggestionIndex);
   9973                     }
   9974                 }
   9975 
   9976                 // Swap text content between actual text and Suggestion span
   9977                 String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
   9978                 suggestions[suggestionInfo.suggestionIndex] = originalText;
   9979 
   9980                 // Restore previous SuggestionSpans
   9981                 final int lengthDifference = suggestion.length() - (spanEnd - spanStart);
   9982                 for (int i = 0; i < length; i++) {
   9983                     // Only spans that include the modified region make sense after replacement
   9984                     // Spans partially included in the replaced region are removed, there is no
   9985                     // way to assign them a valid range after replacement
   9986                     if (suggestionSpansStarts[i] <= spanStart &&
   9987                             suggestionSpansEnds[i] >= spanEnd) {
   9988                         setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i],
   9989                                 suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]);
   9990                     }
   9991                 }
   9992 
   9993                 // Move cursor at the end of the replaced word
   9994                 final int newCursorPosition = spanEnd + lengthDifference;
   9995                 setCursorPosition_internal(newCursorPosition, newCursorPosition);
   9996             }
   9997 
   9998             hide();
   9999         }
   10000     }
   10001 
   10002     /**
   10003      * Removes the suggestion spans.
   10004      */
   10005     CharSequence removeSuggestionSpans(CharSequence text) {
   10006        if (text instanceof Spanned) {
   10007            Spannable spannable;
   10008            if (text instanceof Spannable) {
   10009                spannable = (Spannable) text;
   10010            } else {
   10011                spannable = new SpannableString(text);
   10012                text = spannable;
   10013            }
   10014 
   10015            SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
   10016            for (int i = 0; i < spans.length; i++) {
   10017                spannable.removeSpan(spans[i]);
   10018            }
   10019        }
   10020        return text;
   10021     }
   10022 
   10023     void showSuggestions() {
   10024         if (mSuggestionsPopupWindow == null) {
   10025             mSuggestionsPopupWindow = new SuggestionsPopupWindow();
   10026         }
   10027         hideControllers();
   10028         mSuggestionsPopupWindow.show();
   10029     }
   10030 
   10031     boolean areSuggestionsShown() {
   10032         return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
   10033     }
   10034 
   10035     /**
   10036      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
   10037      * by the IME or by the spell checker as the user types. This is done by adding
   10038      * {@link SuggestionSpan}s to the text.
   10039      *
   10040      * When suggestions are enabled (default), this list of suggestions will be displayed when the
   10041      * user asks for them on these parts of the text. This value depends on the inputType of this
   10042      * TextView.
   10043      *
   10044      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
   10045      *
   10046      * In addition, the type variation must be one of
   10047      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
   10048      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
   10049      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
   10050      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
   10051      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
   10052      *
   10053      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
   10054      *
   10055      * @return true if the suggestions popup window is enabled, based on the inputType.
   10056      */
   10057     public boolean isSuggestionsEnabled() {
   10058         if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
   10059         if ((mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
   10060 
   10061         final int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
   10062         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
   10063                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
   10064                 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
   10065                 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
   10066                 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
   10067     }
   10068 
   10069     /**
   10070      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
   10071      * selection is initiated in this View.
   10072      *
   10073      * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
   10074      * Paste actions, depending on what this View supports.
   10075      *
   10076      * A custom implementation can add new entries in the default menu in its
   10077      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
   10078      * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
   10079      * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
   10080      * or {@link android.R.id#paste} ids as parameters.
   10081      *
   10082      * Returning false from
   10083      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
   10084      * the action mode from being started.
   10085      *
   10086      * Action click events should be handled by the custom implementation of
   10087      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
   10088      *
   10089      * Note that text selection mode is not started when a TextView receives focus and the
   10090      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
   10091      * that case, to allow for quick replacement.
   10092      */
   10093     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
   10094         mCustomSelectionActionModeCallback = actionModeCallback;
   10095     }
   10096 
   10097     /**
   10098      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
   10099      *
   10100      * @return The current custom selection callback.
   10101      */
   10102     public ActionMode.Callback getCustomSelectionActionModeCallback() {
   10103         return mCustomSelectionActionModeCallback;
   10104     }
   10105 
   10106     /**
   10107      *
   10108      * @return true if the selection mode was actually started.
   10109      */
   10110     private boolean startSelectionActionMode() {
   10111         if (mSelectionActionMode != null) {
   10112             // Selection action mode is already started
   10113             return false;
   10114         }
   10115 
   10116         if (!canSelectText() || !requestFocus()) {
   10117             Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
   10118             return false;
   10119         }
   10120 
   10121         if (!hasSelection()) {
   10122             // There may already be a selection on device rotation
   10123             if (!selectCurrentWord()) {
   10124                 // No word found under cursor or text selection not permitted.
   10125                 return false;
   10126             }
   10127         }
   10128 
   10129         boolean willExtract = extractedTextModeWillBeStarted();
   10130 
   10131         // Do not start the action mode when extracted text will show up full screen, thus
   10132         // immediately hiding the newly created action bar, which would be visually distracting.
   10133         if (!willExtract) {
   10134             ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
   10135             mSelectionActionMode = startActionMode(actionModeCallback);
   10136         }
   10137 
   10138         final boolean selectionStarted = mSelectionActionMode != null || willExtract;
   10139         if (selectionStarted && !mTextIsSelectable && mSoftInputShownOnFocus) {
   10140             // Show the IME to be able to replace text, except when selecting non editable text.
   10141             final InputMethodManager imm = InputMethodManager.peekInstance();
   10142             if (imm != null) {
   10143                 imm.showSoftInput(this, 0, null);
   10144             }
   10145         }
   10146 
   10147         return selectionStarted;
   10148     }
   10149 
   10150     private boolean extractedTextModeWillBeStarted() {
   10151         if (!(this instanceof ExtractEditText)) {
   10152             final InputMethodManager imm = InputMethodManager.peekInstance();
   10153             return  imm != null && imm.isFullscreenMode();
   10154         }
   10155         return false;
   10156     }
   10157 
   10158     private void stopSelectionActionMode() {
   10159         if (mSelectionActionMode != null) {
   10160             // This will hide the mSelectionModifierCursorController
   10161             mSelectionActionMode.finish();
   10162         }
   10163     }
   10164 
   10165     /**
   10166      * Paste clipboard content between min and max positions.
   10167      */
   10168     private void paste(int min, int max) {
   10169         ClipboardManager clipboard =
   10170             (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
   10171         ClipData clip = clipboard.getPrimaryClip();
   10172         if (clip != null) {
   10173             boolean didFirst = false;
   10174             for (int i=0; i<clip.getItemCount(); i++) {
   10175                 CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
   10176                 if (paste != null) {
   10177                     if (!didFirst) {
   10178                         long minMax = prepareSpacesAroundPaste(min, max, paste);
   10179                         min = extractRangeStartFromLong(minMax);
   10180                         max = extractRangeEndFromLong(minMax);
   10181                         Selection.setSelection((Spannable) mText, max);
   10182                         ((Editable) mText).replace(min, max, paste);
   10183                         didFirst = true;
   10184                     } else {
   10185                         ((Editable) mText).insert(getSelectionEnd(), "\n");
   10186                         ((Editable) mText).insert(getSelectionEnd(), paste);
   10187                     }
   10188                 }
   10189             }
   10190             stopSelectionActionMode();
   10191             sLastCutOrCopyTime = 0;
   10192         }
   10193     }
   10194 
   10195     private void setPrimaryClip(ClipData clip) {
   10196         ClipboardManager clipboard = (ClipboardManager) getContext().
   10197                 getSystemService(Context.CLIPBOARD_SERVICE);
   10198         clipboard.setPrimaryClip(clip);
   10199         sLastCutOrCopyTime = SystemClock.uptimeMillis();
   10200     }
   10201 
   10202     /**
   10203      * An ActionMode Callback class that is used to provide actions while in text selection mode.
   10204      *
   10205      * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
   10206      * on which of these this TextView supports.
   10207      */
   10208     private class SelectionActionModeCallback implements ActionMode.Callback {
   10209 
   10210         @Override
   10211         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
   10212             TypedArray styledAttributes = mContext.obtainStyledAttributes(
   10213                     com.android.internal.R.styleable.SelectionModeDrawables);
   10214 
   10215             boolean allowText = getContext().getResources().getBoolean(
   10216                     com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
   10217 
   10218             mode.setTitle(allowText ?
   10219                     mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
   10220             mode.setSubtitle(null);
   10221 
   10222             int selectAllIconId = 0; // No icon by default
   10223             if (!allowText) {
   10224                 // Provide an icon, text will not be displayed on smaller screens.
   10225                 selectAllIconId = styledAttributes.getResourceId(
   10226                         R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0);
   10227             }
   10228 
   10229             menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
   10230                     setIcon(selectAllIconId).
   10231                     setAlphabeticShortcut('a').
   10232                     setShowAsAction(
   10233                             MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
   10234 
   10235             if (canCut()) {
   10236                 menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
   10237                     setIcon(styledAttributes.getResourceId(
   10238                             R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)).
   10239                     setAlphabeticShortcut('x').
   10240                     setShowAsAction(
   10241                             MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
   10242             }
   10243 
   10244             if (canCopy()) {
   10245                 menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
   10246                     setIcon(styledAttributes.getResourceId(
   10247                             R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)).
   10248                     setAlphabeticShortcut('c').
   10249                     setShowAsAction(
   10250                             MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
   10251             }
   10252 
   10253             if (canPaste()) {
   10254                 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
   10255                         setIcon(styledAttributes.getResourceId(
   10256                                 R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)).
   10257                         setAlphabeticShortcut('v').
   10258                         setShowAsAction(
   10259                                 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
   10260             }
   10261 
   10262             styledAttributes.recycle();
   10263 
   10264             if (mCustomSelectionActionModeCallback != null) {
   10265                 if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
   10266                     // The custom mode can choose to cancel the action mode
   10267                     return false;
   10268                 }
   10269             }
   10270 
   10271             if (menu.hasVisibleItems() || mode.getCustomView() != null) {
   10272                 getSelectionController().show();
   10273                 return true;
   10274             } else {
   10275                 return false;
   10276             }
   10277         }
   10278 
   10279         @Override
   10280         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
   10281             if (mCustomSelectionActionModeCallback != null) {
   10282                 return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
   10283             }
   10284             return true;
   10285         }
   10286 
   10287         @Override
   10288         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
   10289             if (mCustomSelectionActionModeCallback != null &&
   10290                  mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
   10291                 return true;
   10292             }
   10293             return onTextContextMenuItem(item.getItemId());
   10294         }
   10295 
   10296         @Override
   10297         public void onDestroyActionMode(ActionMode mode) {
   10298             if (mCustomSelectionActionModeCallback != null) {
   10299                 mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
   10300             }
   10301             Selection.setSelection((Spannable) mText, getSelectionEnd());
   10302 
   10303             if (mSelectionModifierCursorController != null) {
   10304                 mSelectionModifierCursorController.hide();
   10305             }
   10306 
   10307             mSelectionActionMode = null;
   10308         }
   10309     }
   10310 
   10311     private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
   10312         private static final int POPUP_TEXT_LAYOUT =
   10313                 com.android.internal.R.layout.text_edit_action_popup_text;
   10314         private TextView mPasteTextView;
   10315         private TextView mReplaceTextView;
   10316 
   10317         @Override
   10318         protected void createPopupWindow() {
   10319             mPopupWindow = new PopupWindow(TextView.this.mContext, null,
   10320                     com.android.internal.R.attr.textSelectHandleWindowStyle);
   10321             mPopupWindow.setClippingEnabled(true);
   10322         }
   10323 
   10324         @Override
   10325         protected void initContentView() {
   10326             LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
   10327             linearLayout.setOrientation(LinearLayout.HORIZONTAL);
   10328             mContentView = linearLayout;
   10329             mContentView.setBackgroundResource(
   10330                     com.android.internal.R.drawable.text_edit_paste_window);
   10331 
   10332             LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
   10333                     getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   10334 
   10335             LayoutParams wrapContent = new LayoutParams(
   10336                     ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
   10337 
   10338             mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
   10339             mPasteTextView.setLayoutParams(wrapContent);
   10340             mContentView.addView(mPasteTextView);
   10341             mPasteTextView.setText(com.android.internal.R.string.paste);
   10342             mPasteTextView.setOnClickListener(this);
   10343 
   10344             mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
   10345             mReplaceTextView.setLayoutParams(wrapContent);
   10346             mContentView.addView(mReplaceTextView);
   10347             mReplaceTextView.setText(com.android.internal.R.string.replace);
   10348             mReplaceTextView.setOnClickListener(this);
   10349         }
   10350 
   10351         @Override
   10352         public void show() {
   10353             boolean canPaste = canPaste();
   10354             boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
   10355             mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
   10356             mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
   10357 
   10358             if (!canPaste && !canSuggest) return;
   10359 
   10360             super.show();
   10361         }
   10362 
   10363         @Override
   10364         public void onClick(View view) {
   10365             if (view == mPasteTextView && canPaste()) {
   10366                 onTextContextMenuItem(ID_PASTE);
   10367                 hide();
   10368             } else if (view == mReplaceTextView) {
   10369                 final int middle = (getSelectionStart() + getSelectionEnd()) / 2;
   10370                 stopSelectionActionMode();
   10371                 Selection.setSelection((Spannable) mText, middle);
   10372                 showSuggestions();
   10373             }
   10374         }
   10375 
   10376         @Override
   10377         protected int getTextOffset() {
   10378             return (getSelectionStart() + getSelectionEnd()) / 2;
   10379         }
   10380 
   10381         @Override
   10382         protected int getVerticalLocalPosition(int line) {
   10383             return mLayout.getLineTop(line) - mContentView.getMeasuredHeight();
   10384         }
   10385 
   10386         @Override
   10387         protected int clipVertically(int positionY) {
   10388             if (positionY < 0) {
   10389                 final int offset = getTextOffset();
   10390                 final int line = mLayout.getLineForOffset(offset);
   10391                 positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line);
   10392                 positionY += mContentView.getMeasuredHeight();
   10393 
   10394                 // Assumes insertion and selection handles share the same height
   10395                 final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes);
   10396                 positionY += handle.getIntrinsicHeight();
   10397             }
   10398 
   10399             return positionY;
   10400         }
   10401     }
   10402 
   10403     private abstract class HandleView extends View implements TextViewPositionListener {
   10404         protected Drawable mDrawable;
   10405         protected Drawable mDrawableLtr;
   10406         protected Drawable mDrawableRtl;
   10407         private final PopupWindow mContainer;
   10408         // Position with respect to the parent TextView
   10409         private int mPositionX, mPositionY;
   10410         private boolean mIsDragging;
   10411         // Offset from touch position to mPosition
   10412         private float mTouchToWindowOffsetX, mTouchToWindowOffsetY;
   10413         protected int mHotspotX;
   10414         // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
   10415         private float mTouchOffsetY;
   10416         // Where the touch position should be on the handle to ensure a maximum cursor visibility
   10417         private float mIdealVerticalOffset;
   10418         // Parent's (TextView) previous position in window
   10419         private int mLastParentX, mLastParentY;
   10420         // Transient action popup window for Paste and Replace actions
   10421         protected ActionPopupWindow mActionPopupWindow;
   10422         // Previous text character offset
   10423         private int mPreviousOffset = -1;
   10424         // Previous text character offset
   10425         private boolean mPositionHasChanged = true;
   10426         // Used to delay the appearance of the action popup window
   10427         private Runnable mActionPopupShower;
   10428 
   10429         public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
   10430             super(TextView.this.mContext);
   10431             mContainer = new PopupWindow(TextView.this.mContext, null,
   10432                     com.android.internal.R.attr.textSelectHandleWindowStyle);
   10433             mContainer.setSplitTouchEnabled(true);
   10434             mContainer.setClippingEnabled(false);
   10435             mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
   10436             mContainer.setContentView(this);
   10437 
   10438             mDrawableLtr = drawableLtr;
   10439             mDrawableRtl = drawableRtl;
   10440 
   10441             updateDrawable();
   10442 
   10443             final int handleHeight = mDrawable.getIntrinsicHeight();
   10444             mTouchOffsetY = -0.3f * handleHeight;
   10445             mIdealVerticalOffset = 0.7f * handleHeight;
   10446         }
   10447 
   10448         protected void updateDrawable() {
   10449             final int offset = getCurrentCursorOffset();
   10450             final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset);
   10451             mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
   10452             mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
   10453         }
   10454 
   10455         protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
   10456 
   10457         // Touch-up filter: number of previous positions remembered
   10458         private static final int HISTORY_SIZE = 5;
   10459         private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
   10460         private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
   10461         private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
   10462         private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
   10463         private int mPreviousOffsetIndex = 0;
   10464         private int mNumberPreviousOffsets = 0;
   10465 
   10466         private void startTouchUpFilter(int offset) {
   10467             mNumberPreviousOffsets = 0;
   10468             addPositionToTouchUpFilter(offset);
   10469         }
   10470 
   10471         private void addPositionToTouchUpFilter(int offset) {
   10472             mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE;
   10473             mPreviousOffsets[mPreviousOffsetIndex] = offset;
   10474             mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis();
   10475             mNumberPreviousOffsets++;
   10476         }
   10477 
   10478         private void filterOnTouchUp() {
   10479             final long now = SystemClock.uptimeMillis();
   10480             int i = 0;
   10481             int index = mPreviousOffsetIndex;
   10482             final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE);
   10483             while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) {
   10484                 i++;
   10485                 index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE;
   10486             }
   10487 
   10488             if (i > 0 && i < iMax &&
   10489                     (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) {
   10490                 positionAtCursorOffset(mPreviousOffsets[index], false);
   10491             }
   10492         }
   10493 
   10494         public boolean offsetHasBeenChanged() {
   10495             return mNumberPreviousOffsets > 1;
   10496         }
   10497 
   10498         @Override
   10499         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   10500             setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
   10501         }
   10502 
   10503         public void show() {
   10504             if (isShowing()) return;
   10505 
   10506             getPositionListener().addSubscriber(this, true /* local position may change */);
   10507 
   10508             // Make sure the offset is always considered new, even when focusing at same position
   10509             mPreviousOffset = -1;
   10510             positionAtCursorOffset(getCurrentCursorOffset(), false);
   10511 
   10512             hideActionPopupWindow();
   10513         }
   10514 
   10515         protected void dismiss() {
   10516             mIsDragging = false;
   10517             mContainer.dismiss();
   10518             onDetached();
   10519         }
   10520 
   10521         public void hide() {
   10522             dismiss();
   10523 
   10524             TextView.this.getPositionListener().removeSubscriber(this);
   10525         }
   10526 
   10527         void showActionPopupWindow(int delay) {
   10528             if (mActionPopupWindow == null) {
   10529                 mActionPopupWindow = new ActionPopupWindow();
   10530             }
   10531             if (mActionPopupShower == null) {
   10532                 mActionPopupShower = new Runnable() {
   10533                     public void run() {
   10534                         mActionPopupWindow.show();
   10535                     }
   10536                 };
   10537             } else {
   10538                 TextView.this.removeCallbacks(mActionPopupShower);
   10539             }
   10540             TextView.this.postDelayed(mActionPopupShower, delay);
   10541         }
   10542 
   10543         protected void hideActionPopupWindow() {
   10544             if (mActionPopupShower != null) {
   10545                 TextView.this.removeCallbacks(mActionPopupShower);
   10546             }
   10547             if (mActionPopupWindow != null) {
   10548                 mActionPopupWindow.hide();
   10549             }
   10550         }
   10551 
   10552         public boolean isShowing() {
   10553             return mContainer.isShowing();
   10554         }
   10555 
   10556         private boolean isVisible() {
   10557             // Always show a dragging handle.
   10558             if (mIsDragging) {
   10559                 return true;
   10560             }
   10561 
   10562             if (isInBatchEditMode()) {
   10563                 return false;
   10564             }
   10565 
   10566             return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY);
   10567         }
   10568 
   10569         public abstract int getCurrentCursorOffset();
   10570 
   10571         protected abstract void updateSelection(int offset);
   10572 
   10573         public abstract void updatePosition(float x, float y);
   10574 
   10575         protected void positionAtCursorOffset(int offset, boolean parentScrolled) {
   10576             // A HandleView relies on the layout, which may be nulled by external methods
   10577             if (mLayout == null) {
   10578                 // Will update controllers' state, hiding them and stopping selection mode if needed
   10579                 prepareCursorControllers();
   10580                 return;
   10581             }
   10582 
   10583             if (offset != mPreviousOffset || parentScrolled) {
   10584                 updateSelection(offset);
   10585                 addPositionToTouchUpFilter(offset);
   10586                 final int line = mLayout.getLineForOffset(offset);
   10587 
   10588                 mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX);
   10589                 mPositionY = mLayout.getLineBottom(line);
   10590 
   10591                 // Take TextView's padding and scroll into account.
   10592                 mPositionX += viewportToContentHorizontalOffset();
   10593                 mPositionY += viewportToContentVerticalOffset();
   10594 
   10595                 mPreviousOffset = offset;
   10596                 mPositionHasChanged = true;
   10597             }
   10598         }
   10599 
   10600         public void updatePosition(int parentPositionX, int parentPositionY,
   10601                 boolean parentPositionChanged, boolean parentScrolled) {
   10602             positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
   10603             if (parentPositionChanged || mPositionHasChanged) {
   10604                 if (mIsDragging) {
   10605                     // Update touchToWindow offset in case of parent scrolling while dragging
   10606                     if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) {
   10607                         mTouchToWindowOffsetX += parentPositionX - mLastParentX;
   10608                         mTouchToWindowOffsetY += parentPositionY - mLastParentY;
   10609                         mLastParentX = parentPositionX;
   10610                         mLastParentY = parentPositionY;
   10611                     }
   10612 
   10613                     onHandleMoved();
   10614                 }
   10615 
   10616                 if (isVisible()) {
   10617                     final int positionX = parentPositionX + mPositionX;
   10618                     final int positionY = parentPositionY + mPositionY;
   10619                     if (isShowing()) {
   10620                         mContainer.update(positionX, positionY, -1, -1);
   10621                     } else {
   10622                         mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY,
   10623                                 positionX, positionY);
   10624                     }
   10625                 } else {
   10626                     if (isShowing()) {
   10627                         dismiss();
   10628                     }
   10629                 }
   10630 
   10631                 mPositionHasChanged = false;
   10632             }
   10633         }
   10634 
   10635         @Override
   10636         protected void onDraw(Canvas c) {
   10637             mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
   10638             mDrawable.draw(c);
   10639         }
   10640 
   10641         @Override
   10642         public boolean onTouchEvent(MotionEvent ev) {
   10643             switch (ev.getActionMasked()) {
   10644                 case MotionEvent.ACTION_DOWN: {
   10645                     startTouchUpFilter(getCurrentCursorOffset());
   10646                     mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
   10647                     mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
   10648 
   10649                     final PositionListener positionListener = getPositionListener();
   10650                     mLastParentX = positionListener.getPositionX();
   10651                     mLastParentY = positionListener.getPositionY();
   10652                     mIsDragging = true;
   10653                     break;
   10654                 }
   10655 
   10656                 case MotionEvent.ACTION_MOVE: {
   10657                     final float rawX = ev.getRawX();
   10658                     final float rawY = ev.getRawY();
   10659 
   10660                     // Vertical hysteresis: vertical down movement tends to snap to ideal offset
   10661                     final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
   10662                     final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
   10663                     float newVerticalOffset;
   10664                     if (previousVerticalOffset < mIdealVerticalOffset) {
   10665                         newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
   10666                         newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
   10667                     } else {
   10668                         newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
   10669                         newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
   10670                     }
   10671                     mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
   10672 
   10673                     final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
   10674                     final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
   10675 
   10676                     updatePosition(newPosX, newPosY);
   10677                     break;
   10678                 }
   10679 
   10680                 case MotionEvent.ACTION_UP:
   10681                     filterOnTouchUp();
   10682                     mIsDragging = false;
   10683                     break;
   10684 
   10685                 case MotionEvent.ACTION_CANCEL:
   10686                     mIsDragging = false;
   10687                     break;
   10688             }
   10689             return true;
   10690         }
   10691 
   10692         public boolean isDragging() {
   10693             return mIsDragging;
   10694         }
   10695 
   10696         void onHandleMoved() {
   10697             hideActionPopupWindow();
   10698         }
   10699 
   10700         public void onDetached() {
   10701             hideActionPopupWindow();
   10702         }
   10703     }
   10704 
   10705     private class InsertionHandleView extends HandleView {
   10706         private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
   10707         private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
   10708 
   10709         // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
   10710         private float mDownPositionX, mDownPositionY;
   10711         private Runnable mHider;
   10712 
   10713         public InsertionHandleView(Drawable drawable) {
   10714             super(drawable, drawable);
   10715         }
   10716 
   10717         @Override
   10718         public void show() {
   10719             super.show();
   10720 
   10721             final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
   10722             if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
   10723                 showActionPopupWindow(0);
   10724             }
   10725 
   10726             hideAfterDelay();
   10727         }
   10728 
   10729         public void showWithActionPopup() {
   10730             show();
   10731             showActionPopupWindow(0);
   10732         }
   10733 
   10734         private void hideAfterDelay() {
   10735             removeHiderCallback();
   10736             if (mHider == null) {
   10737                 mHider = new Runnable() {
   10738                     public void run() {
   10739                         hide();
   10740                     }
   10741                 };
   10742             }
   10743             TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
   10744         }
   10745 
   10746         private void removeHiderCallback() {
   10747             if (mHider != null) {
   10748                 TextView.this.removeCallbacks(mHider);
   10749             }
   10750         }
   10751 
   10752         @Override
   10753         protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
   10754             return drawable.getIntrinsicWidth() / 2;
   10755         }
   10756 
   10757         @Override
   10758         public boolean onTouchEvent(MotionEvent ev) {
   10759             final boolean result = super.onTouchEvent(ev);
   10760 
   10761             switch (ev.getActionMasked()) {
   10762                 case MotionEvent.ACTION_DOWN:
   10763                     mDownPositionX = ev.getRawX();
   10764                     mDownPositionY = ev.getRawY();
   10765                     break;
   10766 
   10767                 case MotionEvent.ACTION_UP:
   10768                     if (!offsetHasBeenChanged()) {
   10769                         final float deltaX = mDownPositionX - ev.getRawX();
   10770                         final float deltaY = mDownPositionY - ev.getRawY();
   10771                         final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
   10772                         if (distanceSquared < mSquaredTouchSlopDistance) {
   10773                             if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
   10774                                 // Tapping on the handle dismisses the displayed action popup
   10775                                 mActionPopupWindow.hide();
   10776                             } else {
   10777                                 showWithActionPopup();
   10778                             }
   10779                         }
   10780                     }
   10781                     hideAfterDelay();
   10782                     break;
   10783 
   10784                 case MotionEvent.ACTION_CANCEL:
   10785                     hideAfterDelay();
   10786                     break;
   10787 
   10788                 default:
   10789                     break;
   10790             }
   10791 
   10792             return result;
   10793         }
   10794 
   10795         @Override
   10796         public int getCurrentCursorOffset() {
   10797             return TextView.this.getSelectionStart();
   10798         }
   10799 
   10800         @Override
   10801         public void updateSelection(int offset) {
   10802             Selection.setSelection((Spannable) mText, offset);
   10803         }
   10804 
   10805         @Override
   10806         public void updatePosition(float x, float y) {
   10807             positionAtCursorOffset(getOffsetForPosition(x, y), false);
   10808         }
   10809 
   10810         @Override
   10811         void onHandleMoved() {
   10812             super.onHandleMoved();
   10813             removeHiderCallback();
   10814         }
   10815 
   10816         @Override
   10817         public void onDetached() {
   10818             super.onDetached();
   10819             removeHiderCallback();
   10820         }
   10821     }
   10822 
   10823     private class SelectionStartHandleView extends HandleView {
   10824 
   10825         public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
   10826             super(drawableLtr, drawableRtl);
   10827         }
   10828 
   10829         @Override
   10830         protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
   10831             if (isRtlRun) {
   10832                 return drawable.getIntrinsicWidth() / 4;
   10833             } else {
   10834                 return (drawable.getIntrinsicWidth() * 3) / 4;
   10835             }
   10836         }
   10837 
   10838         @Override
   10839         public int getCurrentCursorOffset() {
   10840             return TextView.this.getSelectionStart();
   10841         }
   10842 
   10843         @Override
   10844         public void updateSelection(int offset) {
   10845             Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
   10846             updateDrawable();
   10847         }
   10848 
   10849         @Override
   10850         public void updatePosition(float x, float y) {
   10851             int offset = getOffsetForPosition(x, y);
   10852 
   10853             // Handles can not cross and selection is at least one character
   10854             final int selectionEnd = getSelectionEnd();
   10855             if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
   10856 
   10857             positionAtCursorOffset(offset, false);
   10858         }
   10859 
   10860         public ActionPopupWindow getActionPopupWindow() {
   10861             return mActionPopupWindow;
   10862         }
   10863     }
   10864 
   10865     private class SelectionEndHandleView extends HandleView {
   10866 
   10867         public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
   10868             super(drawableLtr, drawableRtl);
   10869         }
   10870 
   10871         @Override
   10872         protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
   10873             if (isRtlRun) {
   10874                 return (drawable.getIntrinsicWidth() * 3) / 4;
   10875             } else {
   10876                 return drawable.getIntrinsicWidth() / 4;
   10877             }
   10878         }
   10879 
   10880         @Override
   10881         public int getCurrentCursorOffset() {
   10882             return TextView.this.getSelectionEnd();
   10883         }
   10884 
   10885         @Override
   10886         public void updateSelection(int offset) {
   10887             Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
   10888             updateDrawable();
   10889         }
   10890 
   10891         @Override
   10892         public void updatePosition(float x, float y) {
   10893             int offset = getOffsetForPosition(x, y);
   10894 
   10895             // Handles can not cross and selection is at least one character
   10896             final int selectionStart = getSelectionStart();
   10897             if (offset <= selectionStart) offset = Math.min(selectionStart + 1, mText.length());
   10898 
   10899             positionAtCursorOffset(offset, false);
   10900         }
   10901 
   10902         public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
   10903             mActionPopupWindow = actionPopupWindow;
   10904         }
   10905     }
   10906 
   10907     /**
   10908      * A CursorController instance can be used to control a cursor in the text.
   10909      * It is not used outside of {@link TextView}.
   10910      * @hide
   10911      */
   10912     private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener {
   10913         /**
   10914          * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
   10915          * See also {@link #hide()}.
   10916          */
   10917         public void show();
   10918 
   10919         /**
   10920          * Hide the cursor controller from screen.
   10921          * See also {@link #show()}.
   10922          */
   10923         public void hide();
   10924 
   10925         /**
   10926          * Called when the view is detached from window. Perform house keeping task, such as
   10927          * stopping Runnable thread that would otherwise keep a reference on the context, thus
   10928          * preventing the activity from being recycled.
   10929          */
   10930         public void onDetached();
   10931     }
   10932 
   10933     private class InsertionPointCursorController implements CursorController {
   10934         private InsertionHandleView mHandle;
   10935 
   10936         public void show() {
   10937             getHandle().show();
   10938         }
   10939 
   10940         public void showWithActionPopup() {
   10941             getHandle().showWithActionPopup();
   10942         }
   10943 
   10944         public void hide() {
   10945             if (mHandle != null) {
   10946                 mHandle.hide();
   10947             }
   10948         }
   10949 
   10950         public void onTouchModeChanged(boolean isInTouchMode) {
   10951             if (!isInTouchMode) {
   10952                 hide();
   10953             }
   10954         }
   10955 
   10956         private InsertionHandleView getHandle() {
   10957             if (mSelectHandleCenter == null) {
   10958                 mSelectHandleCenter = mContext.getResources().getDrawable(
   10959                         mTextSelectHandleRes);
   10960             }
   10961             if (mHandle == null) {
   10962                 mHandle = new InsertionHandleView(mSelectHandleCenter);
   10963             }
   10964             return mHandle;
   10965         }
   10966 
   10967         @Override
   10968         public void onDetached() {
   10969             final ViewTreeObserver observer = getViewTreeObserver();
   10970             observer.removeOnTouchModeChangeListener(this);
   10971 
   10972             if (mHandle != null) mHandle.onDetached();
   10973         }
   10974     }
   10975 
   10976     private class SelectionModifierCursorController implements CursorController {
   10977         private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
   10978         // The cursor controller handles, lazily created when shown.
   10979         private SelectionStartHandleView mStartHandle;
   10980         private SelectionEndHandleView mEndHandle;
   10981         // The offsets of that last touch down event. Remembered to start selection there.
   10982         private int mMinTouchOffset, mMaxTouchOffset;
   10983 
   10984         // Double tap detection
   10985         private long mPreviousTapUpTime = 0;
   10986         private float mPreviousTapPositionX, mPreviousTapPositionY;
   10987 
   10988         SelectionModifierCursorController() {
   10989             resetTouchOffsets();
   10990         }
   10991 
   10992         public void show() {
   10993             if (isInBatchEditMode()) {
   10994                 return;
   10995             }
   10996             initDrawables();
   10997             initHandles();
   10998             hideInsertionPointCursorController();
   10999         }
   11000 
   11001         private void initDrawables() {
   11002             if (mSelectHandleLeft == null) {
   11003                 mSelectHandleLeft = mContext.getResources().getDrawable(
   11004                         mTextSelectHandleLeftRes);
   11005             }
   11006             if (mSelectHandleRight == null) {
   11007                 mSelectHandleRight = mContext.getResources().getDrawable(
   11008                         mTextSelectHandleRightRes);
   11009             }
   11010         }
   11011 
   11012         private void initHandles() {
   11013             // Lazy object creation has to be done before updatePosition() is called.
   11014             if (mStartHandle == null) {
   11015                 mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
   11016             }
   11017             if (mEndHandle == null) {
   11018                 mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
   11019             }
   11020 
   11021             mStartHandle.show();
   11022             mEndHandle.show();
   11023 
   11024             // Make sure both left and right handles share the same ActionPopupWindow (so that
   11025             // moving any of the handles hides the action popup).
   11026             mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
   11027             mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
   11028 
   11029             hideInsertionPointCursorController();
   11030         }
   11031 
   11032         public void hide() {
   11033             if (mStartHandle != null) mStartHandle.hide();
   11034             if (mEndHandle != null) mEndHandle.hide();
   11035         }
   11036 
   11037         public void onTouchEvent(MotionEvent event) {
   11038             // This is done even when the View does not have focus, so that long presses can start
   11039             // selection and tap can move cursor from this tap position.
   11040             switch (event.getActionMasked()) {
   11041                 case MotionEvent.ACTION_DOWN:
   11042                     final float x = event.getX();
   11043                     final float y = event.getY();
   11044 
   11045                     // Remember finger down position, to be able to start selection from there
   11046                     mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
   11047 
   11048                     // Double tap detection
   11049                     long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
   11050                     if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
   11051                             isPositionOnText(x, y)) {
   11052                         final float deltaX = x - mPreviousTapPositionX;
   11053                         final float deltaY = y - mPreviousTapPositionY;
   11054                         final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
   11055                         if (distanceSquared < mSquaredTouchSlopDistance) {
   11056                             startSelectionActionMode();
   11057                             mDiscardNextActionUp = true;
   11058                         }
   11059                     }
   11060 
   11061                     mPreviousTapPositionX = x;
   11062                     mPreviousTapPositionY = y;
   11063                     break;
   11064 
   11065                 case MotionEvent.ACTION_POINTER_DOWN:
   11066                 case MotionEvent.ACTION_POINTER_UP:
   11067                     // Handle multi-point gestures. Keep min and max offset positions.
   11068                     // Only activated for devices that correctly handle multi-touch.
   11069                     if (mContext.getPackageManager().hasSystemFeature(
   11070                             PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
   11071                         updateMinAndMaxOffsets(event);
   11072                     }
   11073                     break;
   11074 
   11075                 case MotionEvent.ACTION_UP:
   11076                     mPreviousTapUpTime = SystemClock.uptimeMillis();
   11077                     break;
   11078             }
   11079         }
   11080 
   11081         /**
   11082          * @param event
   11083          */
   11084         private void updateMinAndMaxOffsets(MotionEvent event) {
   11085             int pointerCount = event.getPointerCount();
   11086             for (int index = 0; index < pointerCount; index++) {
   11087                 int offset = getOffsetForPosition(event.getX(index), event.getY(index));
   11088                 if (offset < mMinTouchOffset) mMinTouchOffset = offset;
   11089                 if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
   11090             }
   11091         }
   11092 
   11093         public int getMinTouchOffset() {
   11094             return mMinTouchOffset;
   11095         }
   11096 
   11097         public int getMaxTouchOffset() {
   11098             return mMaxTouchOffset;
   11099         }
   11100 
   11101         public void resetTouchOffsets() {
   11102             mMinTouchOffset = mMaxTouchOffset = -1;
   11103         }
   11104 
   11105         /**
   11106          * @return true iff this controller is currently used to move the selection start.
   11107          */
   11108         public boolean isSelectionStartDragged() {
   11109             return mStartHandle != null && mStartHandle.isDragging();
   11110         }
   11111 
   11112         public void onTouchModeChanged(boolean isInTouchMode) {
   11113             if (!isInTouchMode) {
   11114                 hide();
   11115             }
   11116         }
   11117 
   11118         @Override
   11119         public void onDetached() {
   11120             final ViewTreeObserver observer = getViewTreeObserver();
   11121             observer.removeOnTouchModeChangeListener(this);
   11122 
   11123             if (mStartHandle != null) mStartHandle.onDetached();
   11124             if (mEndHandle != null) mEndHandle.onDetached();
   11125         }
   11126     }
   11127 
   11128     private void hideInsertionPointCursorController() {
   11129         // No need to create the controller to hide it.
   11130         if (mInsertionPointCursorController != null) {
   11131             mInsertionPointCursorController.hide();
   11132         }
   11133     }
   11134 
   11135     /**
   11136      * Hides the insertion controller and stops text selection mode, hiding the selection controller
   11137      */
   11138     private void hideControllers() {
   11139         hideCursorControllers();
   11140         hideSpanControllers();
   11141     }
   11142 
   11143     private void hideSpanControllers() {
   11144         if (mChangeWatcher != null) {
   11145             mChangeWatcher.hideControllers();
   11146         }
   11147     }
   11148 
   11149     private void hideCursorControllers() {
   11150         if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) {
   11151             // Should be done before hide insertion point controller since it triggers a show of it
   11152             mSuggestionsPopupWindow.hide();
   11153         }
   11154         hideInsertionPointCursorController();
   11155         stopSelectionActionMode();
   11156     }
   11157 
   11158     /**
   11159      * Get the character offset closest to the specified absolute position. A typical use case is to
   11160      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
   11161      *
   11162      * @param x The horizontal absolute position of a point on screen
   11163      * @param y The vertical absolute position of a point on screen
   11164      * @return the character offset for the character whose position is closest to the specified
   11165      *  position. Returns -1 if there is no layout.
   11166      */
   11167     public int getOffsetForPosition(float x, float y) {
   11168         if (getLayout() == null) return -1;
   11169         final int line = getLineAtCoordinate(y);
   11170         final int offset = getOffsetAtCoordinate(line, x);
   11171         return offset;
   11172     }
   11173 
   11174     private float convertToLocalHorizontalCoordinate(float x) {
   11175         x -= getTotalPaddingLeft();
   11176         // Clamp the position to inside of the view.
   11177         x = Math.max(0.0f, x);
   11178         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
   11179         x += getScrollX();
   11180         return x;
   11181     }
   11182 
   11183     private int getLineAtCoordinate(float y) {
   11184         y -= getTotalPaddingTop();
   11185         // Clamp the position to inside of the view.
   11186         y = Math.max(0.0f, y);
   11187         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
   11188         y += getScrollY();
   11189         return getLayout().getLineForVertical((int) y);
   11190     }
   11191 
   11192     private int getOffsetAtCoordinate(int line, float x) {
   11193         x = convertToLocalHorizontalCoordinate(x);
   11194         return getLayout().getOffsetForHorizontal(line, x);
   11195     }
   11196 
   11197     /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
   11198      * in the view. Returns false when the position is in the empty space of left/right of text.
   11199      */
   11200     private boolean isPositionOnText(float x, float y) {
   11201         if (getLayout() == null) return false;
   11202 
   11203         final int line = getLineAtCoordinate(y);
   11204         x = convertToLocalHorizontalCoordinate(x);
   11205 
   11206         if (x < getLayout().getLineLeft(line)) return false;
   11207         if (x > getLayout().getLineRight(line)) return false;
   11208         return true;
   11209     }
   11210 
   11211     @Override
   11212     public boolean onDragEvent(DragEvent event) {
   11213         switch (event.getAction()) {
   11214             case DragEvent.ACTION_DRAG_STARTED:
   11215                 return hasInsertionController();
   11216 
   11217             case DragEvent.ACTION_DRAG_ENTERED:
   11218                 TextView.this.requestFocus();
   11219                 return true;
   11220 
   11221             case DragEvent.ACTION_DRAG_LOCATION:
   11222                 final int offset = getOffsetForPosition(event.getX(), event.getY());
   11223                 Selection.setSelection((Spannable)mText, offset);
   11224                 return true;
   11225 
   11226             case DragEvent.ACTION_DROP:
   11227                 onDrop(event);
   11228                 return true;
   11229 
   11230             case DragEvent.ACTION_DRAG_ENDED:
   11231             case DragEvent.ACTION_DRAG_EXITED:
   11232             default:
   11233                 return true;
   11234         }
   11235     }
   11236 
   11237     private void onDrop(DragEvent event) {
   11238         StringBuilder content = new StringBuilder("");
   11239         ClipData clipData = event.getClipData();
   11240         final int itemCount = clipData.getItemCount();
   11241         for (int i=0; i < itemCount; i++) {
   11242             Item item = clipData.getItemAt(i);
   11243             content.append(item.coerceToText(TextView.this.mContext));
   11244         }
   11245 
   11246         final int offset = getOffsetForPosition(event.getX(), event.getY());
   11247 
   11248         Object localState = event.getLocalState();
   11249         DragLocalState dragLocalState = null;
   11250         if (localState instanceof DragLocalState) {
   11251             dragLocalState = (DragLocalState) localState;
   11252         }
   11253         boolean dragDropIntoItself = dragLocalState != null &&
   11254                 dragLocalState.sourceTextView == this;
   11255 
   11256         if (dragDropIntoItself) {
   11257             if (offset >= dragLocalState.start && offset < dragLocalState.end) {
   11258                 // A drop inside the original selection discards the drop.
   11259                 return;
   11260             }
   11261         }
   11262 
   11263         final int originalLength = mText.length();
   11264         long minMax = prepareSpacesAroundPaste(offset, offset, content);
   11265         int min = extractRangeStartFromLong(minMax);
   11266         int max = extractRangeEndFromLong(minMax);
   11267 
   11268         Selection.setSelection((Spannable) mText, max);
   11269         replaceText_internal(min, max, content);
   11270 
   11271         if (dragDropIntoItself) {
   11272             int dragSourceStart = dragLocalState.start;
   11273             int dragSourceEnd = dragLocalState.end;
   11274             if (max <= dragSourceStart) {
   11275                 // Inserting text before selection has shifted positions
   11276                 final int shift = mText.length() - originalLength;
   11277                 dragSourceStart += shift;
   11278                 dragSourceEnd += shift;
   11279             }
   11280 
   11281             // Delete original selection
   11282             deleteText_internal(dragSourceStart, dragSourceEnd);
   11283 
   11284             // Make sure we do not leave two adjacent spaces.
   11285             if ((dragSourceStart == 0 ||
   11286                     Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
   11287                     (dragSourceStart == mText.length() ||
   11288                     Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
   11289                 final int pos = dragSourceStart == mText.length() ?
   11290                         dragSourceStart - 1 : dragSourceStart;
   11291                 deleteText_internal(pos, pos + 1);
   11292             }
   11293         }
   11294     }
   11295 
   11296     /**
   11297      * @return True if this view supports insertion handles.
   11298      */
   11299     boolean hasInsertionController() {
   11300         return mInsertionControllerEnabled;
   11301     }
   11302 
   11303     /**
   11304      * @return True if this view supports selection handles.
   11305      */
   11306     boolean hasSelectionController() {
   11307         return mSelectionControllerEnabled;
   11308     }
   11309 
   11310     InsertionPointCursorController getInsertionController() {
   11311         if (!mInsertionControllerEnabled) {
   11312             return null;
   11313         }
   11314 
   11315         if (mInsertionPointCursorController == null) {
   11316             mInsertionPointCursorController = new InsertionPointCursorController();
   11317 
   11318             final ViewTreeObserver observer = getViewTreeObserver();
   11319             observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
   11320         }
   11321 
   11322         return mInsertionPointCursorController;
   11323     }
   11324 
   11325     SelectionModifierCursorController getSelectionController() {
   11326         if (!mSelectionControllerEnabled) {
   11327             return null;
   11328         }
   11329 
   11330         if (mSelectionModifierCursorController == null) {
   11331             mSelectionModifierCursorController = new SelectionModifierCursorController();
   11332 
   11333             final ViewTreeObserver observer = getViewTreeObserver();
   11334             observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
   11335         }
   11336 
   11337         return mSelectionModifierCursorController;
   11338     }
   11339 
   11340     boolean isInBatchEditMode() {
   11341         final InputMethodState ims = mInputMethodState;
   11342         if (ims != null) {
   11343             return ims.mBatchEditNesting > 0;
   11344         }
   11345         return mInBatchEditControllers;
   11346     }
   11347 
   11348     @Override
   11349     protected void resolveTextDirection() {
   11350         if (hasPasswordTransformationMethod()) {
   11351             mTextDir = TextDirectionHeuristics.LOCALE;
   11352             return;
   11353         }
   11354 
   11355         // Always need to resolve layout direction first
   11356         final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
   11357 
   11358         // Then resolve text direction on the parent
   11359         super.resolveTextDirection();
   11360 
   11361         // Now, we can select the heuristic
   11362         int textDir = getResolvedTextDirection();
   11363         switch (textDir) {
   11364             default:
   11365             case TEXT_DIRECTION_FIRST_STRONG:
   11366                 mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
   11367                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
   11368                 break;
   11369             case TEXT_DIRECTION_ANY_RTL:
   11370                 mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
   11371                 break;
   11372             case TEXT_DIRECTION_LTR:
   11373                 mTextDir = TextDirectionHeuristics.LTR;
   11374                 break;
   11375             case TEXT_DIRECTION_RTL:
   11376                 mTextDir = TextDirectionHeuristics.RTL;
   11377                 break;
   11378         }
   11379     }
   11380 
   11381     /**
   11382      * Subclasses will need to override this method to implement their own way of resolving
   11383      * drawables depending on the layout direction.
   11384      *
   11385      * A call to the super method will be required from the subclasses implementation.
   11386      *
   11387      */
   11388     protected void resolveDrawables() {
   11389         // No need to resolve twice
   11390         if (mResolvedDrawables) {
   11391             return;
   11392         }
   11393         // No drawable to resolve
   11394         if (mDrawables == null) {
   11395             return;
   11396         }
   11397         // No relative drawable to resolve
   11398         if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
   11399             mResolvedDrawables = true;
   11400             return;
   11401         }
   11402 
   11403         Drawables dr = mDrawables;
   11404         switch(getResolvedLayoutDirection()) {
   11405             case LAYOUT_DIRECTION_RTL:
   11406                 if (dr.mDrawableStart != null) {
   11407                     dr.mDrawableRight = dr.mDrawableStart;
   11408 
   11409                     dr.mDrawableSizeRight = dr.mDrawableSizeStart;
   11410                     dr.mDrawableHeightRight = dr.mDrawableHeightStart;
   11411                 }
   11412                 if (dr.mDrawableEnd != null) {
   11413                     dr.mDrawableLeft = dr.mDrawableEnd;
   11414 
   11415                     dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
   11416                     dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
   11417                 }
   11418                 break;
   11419 
   11420             case LAYOUT_DIRECTION_LTR:
   11421             default:
   11422                 if (dr.mDrawableStart != null) {
   11423                     dr.mDrawableLeft = dr.mDrawableStart;
   11424 
   11425                     dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
   11426                     dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
   11427                 }
   11428                 if (dr.mDrawableEnd != null) {
   11429                     dr.mDrawableRight = dr.mDrawableEnd;
   11430 
   11431                     dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
   11432                     dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
   11433                 }
   11434                 break;
   11435         }
   11436         mResolvedDrawables = true;
   11437     }
   11438 
   11439     protected void resetResolvedDrawables() {
   11440         mResolvedDrawables = false;
   11441     }
   11442 
   11443     /**
   11444      * @hide
   11445      */
   11446     protected void viewClicked(InputMethodManager imm) {
   11447         if (imm != null) {
   11448             imm.viewClicked(this);
   11449         }
   11450     }
   11451 
   11452     /**
   11453      * Deletes the range of text [start, end[.
   11454      * @hide
   11455      */
   11456     protected void deleteText_internal(int start, int end) {
   11457         ((Editable) mText).delete(start, end);
   11458     }
   11459 
   11460     /**
   11461      * Replaces the range of text [start, end[ by replacement text
   11462      * @hide
   11463      */
   11464     protected void replaceText_internal(int start, int end, CharSequence text) {
   11465         ((Editable) mText).replace(start, end, text);
   11466     }
   11467 
   11468     /**
   11469      * Sets a span on the specified range of text
   11470      * @hide
   11471      */
   11472     protected void setSpan_internal(Object span, int start, int end, int flags) {
   11473         ((Editable) mText).setSpan(span, start, end, flags);
   11474     }
   11475 
   11476     /**
   11477      * Moves the cursor to the specified offset position in text
   11478      * @hide
   11479      */
   11480     protected void setCursorPosition_internal(int start, int end) {
   11481         Selection.setSelection(((Editable) mText), start, end);
   11482     }
   11483 
   11484     @ViewDebug.ExportedProperty(category = "text")
   11485     private CharSequence            mText;
   11486     private CharSequence            mTransformed;
   11487     private BufferType              mBufferType = BufferType.NORMAL;
   11488 
   11489     private int                     mInputType = EditorInfo.TYPE_NULL;
   11490     private CharSequence            mHint;
   11491     private Layout                  mHintLayout;
   11492 
   11493     private KeyListener             mInput;
   11494 
   11495     private MovementMethod          mMovement;
   11496     private TransformationMethod    mTransformation;
   11497     private boolean                 mAllowTransformationLengthChange;
   11498     private ChangeWatcher           mChangeWatcher;
   11499 
   11500     private ArrayList<TextWatcher>  mListeners = null;
   11501 
   11502     // display attributes
   11503     private final TextPaint         mTextPaint;
   11504     private boolean                 mUserSetTextScaleX;
   11505     private final Paint             mHighlightPaint;
   11506     private int                     mHighlightColor = 0x6633B5E5;
   11507     /**
   11508      * This is temporarily visible to fix bug 3085564 in webView. Do not rely on
   11509      * this field being protected. Will be restored as private when lineHeight
   11510      * feature request 3215097 is implemented
   11511      * @hide
   11512      */
   11513     protected Layout                mLayout;
   11514 
   11515     private long                    mShowCursor;
   11516     private Blink                   mBlink;
   11517     private boolean                 mCursorVisible = true;
   11518 
   11519     // Cursor Controllers.
   11520     private InsertionPointCursorController mInsertionPointCursorController;
   11521     private SelectionModifierCursorController mSelectionModifierCursorController;
   11522     private ActionMode              mSelectionActionMode;
   11523     private boolean                 mInsertionControllerEnabled;
   11524     private boolean                 mSelectionControllerEnabled;
   11525     private boolean                 mInBatchEditControllers;
   11526 
   11527     private boolean                 mSelectAllOnFocus = false;
   11528 
   11529     private int                     mGravity = Gravity.TOP | Gravity.START;
   11530     private boolean                 mHorizontallyScrolling;
   11531 
   11532     private int                     mAutoLinkMask;
   11533     private boolean                 mLinksClickable = true;
   11534 
   11535     private float                   mSpacingMult = 1.0f;
   11536     private float                   mSpacingAdd = 0.0f;
   11537     private boolean                 mTextIsSelectable = false;
   11538 
   11539     private static final int        LINES = 1;
   11540     private static final int        EMS = LINES;
   11541     private static final int        PIXELS = 2;
   11542 
   11543     private int                     mMaximum = Integer.MAX_VALUE;
   11544     private int                     mMaxMode = LINES;
   11545     private int                     mMinimum = 0;
   11546     private int                     mMinMode = LINES;
   11547 
   11548     private int                     mOldMaximum = mMaximum;
   11549     private int                     mOldMaxMode = mMaxMode;
   11550 
   11551     private int                     mMaxWidth = Integer.MAX_VALUE;
   11552     private int                     mMaxWidthMode = PIXELS;
   11553     private int                     mMinWidth = 0;
   11554     private int                     mMinWidthMode = PIXELS;
   11555 
   11556     private boolean                 mSingleLine;
   11557     private int                     mDesiredHeightAtMeasure = -1;
   11558     private boolean                 mIncludePad = true;
   11559 
   11560     // tmp primitives, so we don't alloc them on each draw
   11561     private Path                    mHighlightPath;
   11562     private boolean                 mHighlightPathBogus = true;
   11563     private static final RectF      sTempRect = new RectF();
   11564     private static final float[]    sTmpPosition = new float[2];
   11565 
   11566     // XXX should be much larger
   11567     private static final int        VERY_WIDE = 1024*1024;
   11568 
   11569     private static final int        BLINK = 500;
   11570 
   11571     private static final int ANIMATED_SCROLL_GAP = 250;
   11572     private long mLastScroll;
   11573     private Scroller mScroller = null;
   11574 
   11575     private BoringLayout.Metrics mBoring;
   11576     private BoringLayout.Metrics mHintBoring;
   11577 
   11578     private BoringLayout mSavedLayout, mSavedHintLayout;
   11579 
   11580     private TextDirectionHeuristic mTextDir = null;
   11581 
   11582     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   11583     private InputFilter[] mFilters = NO_FILTERS;
   11584     private static final Spanned EMPTY_SPANNED = new SpannedString("");
   11585     private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
   11586     // System wide time for last cut or copy action.
   11587     private static long sLastCutOrCopyTime;
   11588     // Used to highlight a word when it is corrected by the IME
   11589     private CorrectionHighlighter mCorrectionHighlighter;
   11590     // New state used to change background based on whether this TextView is multiline.
   11591     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
   11592 }
   11593