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