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      * @param color A color value in the form 0xAARRGGBB.
   3238      * Do not pass a resource ID. To get a color value from a resource ID, call
   3239      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
   3240      *
   3241      * @see #setTextColor(ColorStateList)
   3242      * @see #getTextColors()
   3243      *
   3244      * @attr ref android.R.styleable#TextView_textColor
   3245      */
   3246     @android.view.RemotableViewMethod
   3247     public void setTextColor(@ColorInt int color) {
   3248         mTextColor = ColorStateList.valueOf(color);
   3249         updateTextColors();
   3250     }
   3251 
   3252     /**
   3253      * Sets the text color.
   3254      *
   3255      * @see #setTextColor(int)
   3256      * @see #getTextColors()
   3257      * @see #setHintTextColor(ColorStateList)
   3258      * @see #setLinkTextColor(ColorStateList)
   3259      *
   3260      * @attr ref android.R.styleable#TextView_textColor
   3261      */
   3262     @android.view.RemotableViewMethod
   3263     public void setTextColor(ColorStateList colors) {
   3264         if (colors == null) {
   3265             throw new NullPointerException();
   3266         }
   3267 
   3268         mTextColor = colors;
   3269         updateTextColors();
   3270     }
   3271 
   3272     /**
   3273      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
   3274      *
   3275      * @see #setTextColor(ColorStateList)
   3276      * @see #setTextColor(int)
   3277      *
   3278      * @attr ref android.R.styleable#TextView_textColor
   3279      */
   3280     public final ColorStateList getTextColors() {
   3281         return mTextColor;
   3282     }
   3283 
   3284     /**
   3285      * <p>Return the current color selected for normal text.</p>
   3286      *
   3287      * @return Returns the current text color.
   3288      */
   3289     @ColorInt
   3290     public final int getCurrentTextColor() {
   3291         return mCurTextColor;
   3292     }
   3293 
   3294     /**
   3295      * Sets the color used to display the selection highlight.
   3296      *
   3297      * @attr ref android.R.styleable#TextView_textColorHighlight
   3298      */
   3299     @android.view.RemotableViewMethod
   3300     public void setHighlightColor(@ColorInt int color) {
   3301         if (mHighlightColor != color) {
   3302             mHighlightColor = color;
   3303             invalidate();
   3304         }
   3305     }
   3306 
   3307     /**
   3308      * @return the color used to display the selection highlight
   3309      *
   3310      * @see #setHighlightColor(int)
   3311      *
   3312      * @attr ref android.R.styleable#TextView_textColorHighlight
   3313      */
   3314     @ColorInt
   3315     public int getHighlightColor() {
   3316         return mHighlightColor;
   3317     }
   3318 
   3319     /**
   3320      * Sets whether the soft input method will be made visible when this
   3321      * TextView gets focused. The default is true.
   3322      */
   3323     @android.view.RemotableViewMethod
   3324     public final void setShowSoftInputOnFocus(boolean show) {
   3325         createEditorIfNeeded();
   3326         mEditor.mShowSoftInputOnFocus = show;
   3327     }
   3328 
   3329     /**
   3330      * Returns whether the soft input method will be made visible when this
   3331      * TextView gets focused. The default is true.
   3332      */
   3333     public final boolean getShowSoftInputOnFocus() {
   3334         // When there is no Editor, return default true value
   3335         return mEditor == null || mEditor.mShowSoftInputOnFocus;
   3336     }
   3337 
   3338     /**
   3339      * Gives the text a shadow of the specified blur radius and color, the specified
   3340      * distance from its drawn position.
   3341      * <p>
   3342      * The text shadow produced does not interact with the properties on view
   3343      * that are responsible for real time shadows,
   3344      * {@link View#getElevation() elevation} and
   3345      * {@link View#getTranslationZ() translationZ}.
   3346      *
   3347      * @see Paint#setShadowLayer(float, float, float, int)
   3348      *
   3349      * @attr ref android.R.styleable#TextView_shadowColor
   3350      * @attr ref android.R.styleable#TextView_shadowDx
   3351      * @attr ref android.R.styleable#TextView_shadowDy
   3352      * @attr ref android.R.styleable#TextView_shadowRadius
   3353      */
   3354     public void setShadowLayer(float radius, float dx, float dy, int color) {
   3355         mTextPaint.setShadowLayer(radius, dx, dy, color);
   3356 
   3357         mShadowRadius = radius;
   3358         mShadowDx = dx;
   3359         mShadowDy = dy;
   3360         mShadowColor = color;
   3361 
   3362         // Will change text clip region
   3363         if (mEditor != null) {
   3364             mEditor.invalidateTextDisplayList();
   3365             mEditor.invalidateHandlesAndActionMode();
   3366         }
   3367         invalidate();
   3368     }
   3369 
   3370     /**
   3371      * Gets the radius of the shadow layer.
   3372      *
   3373      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
   3374      *
   3375      * @see #setShadowLayer(float, float, float, int)
   3376      *
   3377      * @attr ref android.R.styleable#TextView_shadowRadius
   3378      */
   3379     public float getShadowRadius() {
   3380         return mShadowRadius;
   3381     }
   3382 
   3383     /**
   3384      * @return the horizontal offset of the shadow layer
   3385      *
   3386      * @see #setShadowLayer(float, float, float, int)
   3387      *
   3388      * @attr ref android.R.styleable#TextView_shadowDx
   3389      */
   3390     public float getShadowDx() {
   3391         return mShadowDx;
   3392     }
   3393 
   3394     /**
   3395      * @return the vertical offset of the shadow layer
   3396      *
   3397      * @see #setShadowLayer(float, float, float, int)
   3398      *
   3399      * @attr ref android.R.styleable#TextView_shadowDy
   3400      */
   3401     public float getShadowDy() {
   3402         return mShadowDy;
   3403     }
   3404 
   3405     /**
   3406      * @return the color of the shadow layer
   3407      *
   3408      * @see #setShadowLayer(float, float, float, int)
   3409      *
   3410      * @attr ref android.R.styleable#TextView_shadowColor
   3411      */
   3412     @ColorInt
   3413     public int getShadowColor() {
   3414         return mShadowColor;
   3415     }
   3416 
   3417     /**
   3418      * @return the base paint used for the text.  Please use this only to
   3419      * consult the Paint's properties and not to change them.
   3420      */
   3421     public TextPaint getPaint() {
   3422         return mTextPaint;
   3423     }
   3424 
   3425     /**
   3426      * Sets the autolink mask of the text.  See {@link
   3427      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   3428      * possible values.
   3429      *
   3430      * @attr ref android.R.styleable#TextView_autoLink
   3431      */
   3432     @android.view.RemotableViewMethod
   3433     public final void setAutoLinkMask(int mask) {
   3434         mAutoLinkMask = mask;
   3435     }
   3436 
   3437     /**
   3438      * Sets whether the movement method will automatically be set to
   3439      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   3440      * set to nonzero and links are detected in {@link #setText}.
   3441      * The default is true.
   3442      *
   3443      * @attr ref android.R.styleable#TextView_linksClickable
   3444      */
   3445     @android.view.RemotableViewMethod
   3446     public final void setLinksClickable(boolean whether) {
   3447         mLinksClickable = whether;
   3448     }
   3449 
   3450     /**
   3451      * Returns whether the movement method will automatically be set to
   3452      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   3453      * set to nonzero and links are detected in {@link #setText}.
   3454      * The default is true.
   3455      *
   3456      * @attr ref android.R.styleable#TextView_linksClickable
   3457      */
   3458     public final boolean getLinksClickable() {
   3459         return mLinksClickable;
   3460     }
   3461 
   3462     /**
   3463      * Returns the list of URLSpans attached to the text
   3464      * (by {@link Linkify} or otherwise) if any.  You can call
   3465      * {@link URLSpan#getURL} on them to find where they link to
   3466      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
   3467      * to find the region of the text they are attached to.
   3468      */
   3469     public URLSpan[] getUrls() {
   3470         if (mText instanceof Spanned) {
   3471             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
   3472         } else {
   3473             return new URLSpan[0];
   3474         }
   3475     }
   3476 
   3477     /**
   3478      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
   3479      * TextView.
   3480      *
   3481      * @see #setHintTextColor(ColorStateList)
   3482      * @see #getHintTextColors()
   3483      * @see #setTextColor(int)
   3484      *
   3485      * @attr ref android.R.styleable#TextView_textColorHint
   3486      */
   3487     @android.view.RemotableViewMethod
   3488     public final void setHintTextColor(@ColorInt int color) {
   3489         mHintTextColor = ColorStateList.valueOf(color);
   3490         updateTextColors();
   3491     }
   3492 
   3493     /**
   3494      * Sets the color of the hint text.
   3495      *
   3496      * @see #getHintTextColors()
   3497      * @see #setHintTextColor(int)
   3498      * @see #setTextColor(ColorStateList)
   3499      * @see #setLinkTextColor(ColorStateList)
   3500      *
   3501      * @attr ref android.R.styleable#TextView_textColorHint
   3502      */
   3503     public final void setHintTextColor(ColorStateList colors) {
   3504         mHintTextColor = colors;
   3505         updateTextColors();
   3506     }
   3507 
   3508     /**
   3509      * @return the color of the hint text, for the different states of this TextView.
   3510      *
   3511      * @see #setHintTextColor(ColorStateList)
   3512      * @see #setHintTextColor(int)
   3513      * @see #setTextColor(ColorStateList)
   3514      * @see #setLinkTextColor(ColorStateList)
   3515      *
   3516      * @attr ref android.R.styleable#TextView_textColorHint
   3517      */
   3518     public final ColorStateList getHintTextColors() {
   3519         return mHintTextColor;
   3520     }
   3521 
   3522     /**
   3523      * <p>Return the current color selected to paint the hint text.</p>
   3524      *
   3525      * @return Returns the current hint text color.
   3526      */
   3527     @ColorInt
   3528     public final int getCurrentHintTextColor() {
   3529         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
   3530     }
   3531 
   3532     /**
   3533      * Sets the color of links in the text.
   3534      *
   3535      * @see #setLinkTextColor(ColorStateList)
   3536      * @see #getLinkTextColors()
   3537      *
   3538      * @attr ref android.R.styleable#TextView_textColorLink
   3539      */
   3540     @android.view.RemotableViewMethod
   3541     public final void setLinkTextColor(@ColorInt int color) {
   3542         mLinkTextColor = ColorStateList.valueOf(color);
   3543         updateTextColors();
   3544     }
   3545 
   3546     /**
   3547      * Sets the color of links in the text.
   3548      *
   3549      * @see #setLinkTextColor(int)
   3550      * @see #getLinkTextColors()
   3551      * @see #setTextColor(ColorStateList)
   3552      * @see #setHintTextColor(ColorStateList)
   3553      *
   3554      * @attr ref android.R.styleable#TextView_textColorLink
   3555      */
   3556     public final void setLinkTextColor(ColorStateList colors) {
   3557         mLinkTextColor = colors;
   3558         updateTextColors();
   3559     }
   3560 
   3561     /**
   3562      * @return the list of colors used to paint the links in the text, for the different states of
   3563      * this TextView
   3564      *
   3565      * @see #setLinkTextColor(ColorStateList)
   3566      * @see #setLinkTextColor(int)
   3567      *
   3568      * @attr ref android.R.styleable#TextView_textColorLink
   3569      */
   3570     public final ColorStateList getLinkTextColors() {
   3571         return mLinkTextColor;
   3572     }
   3573 
   3574     /**
   3575      * Sets the horizontal alignment of the text and the
   3576      * vertical gravity that will be used when there is extra space
   3577      * in the TextView beyond what is required for the text itself.
   3578      *
   3579      * @see android.view.Gravity
   3580      * @attr ref android.R.styleable#TextView_gravity
   3581      */
   3582     public void setGravity(int gravity) {
   3583         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
   3584             gravity |= Gravity.START;
   3585         }
   3586         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
   3587             gravity |= Gravity.TOP;
   3588         }
   3589 
   3590         boolean newLayout = false;
   3591 
   3592         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
   3593             (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
   3594             newLayout = true;
   3595         }
   3596 
   3597         if (gravity != mGravity) {
   3598             invalidate();
   3599         }
   3600 
   3601         mGravity = gravity;
   3602 
   3603         if (mLayout != null && newLayout) {
   3604             // XXX this is heavy-handed because no actual content changes.
   3605             int want = mLayout.getWidth();
   3606             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   3607 
   3608             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   3609                           mRight - mLeft - getCompoundPaddingLeft() -
   3610                           getCompoundPaddingRight(), true);
   3611         }
   3612     }
   3613 
   3614     /**
   3615      * Returns the horizontal and vertical alignment of this TextView.
   3616      *
   3617      * @see android.view.Gravity
   3618      * @attr ref android.R.styleable#TextView_gravity
   3619      */
   3620     public int getGravity() {
   3621         return mGravity;
   3622     }
   3623 
   3624     /**
   3625      * @return the flags on the Paint being used to display the text.
   3626      * @see Paint#getFlags
   3627      */
   3628     public int getPaintFlags() {
   3629         return mTextPaint.getFlags();
   3630     }
   3631 
   3632     /**
   3633      * Sets flags on the Paint being used to display the text and
   3634      * reflows the text if they are different from the old flags.
   3635      * @see Paint#setFlags
   3636      */
   3637     @android.view.RemotableViewMethod
   3638     public void setPaintFlags(int flags) {
   3639         if (mTextPaint.getFlags() != flags) {
   3640             mTextPaint.setFlags(flags);
   3641 
   3642             if (mLayout != null) {
   3643                 nullLayouts();
   3644                 requestLayout();
   3645                 invalidate();
   3646             }
   3647         }
   3648     }
   3649 
   3650     /**
   3651      * Sets whether the text should be allowed to be wider than the
   3652      * View is.  If false, it will be wrapped to the width of the View.
   3653      *
   3654      * @attr ref android.R.styleable#TextView_scrollHorizontally
   3655      */
   3656     public void setHorizontallyScrolling(boolean whether) {
   3657         if (mHorizontallyScrolling != whether) {
   3658             mHorizontallyScrolling = whether;
   3659 
   3660             if (mLayout != null) {
   3661                 nullLayouts();
   3662                 requestLayout();
   3663                 invalidate();
   3664             }
   3665         }
   3666     }
   3667 
   3668     /**
   3669      * Returns whether the text is allowed to be wider than the View is.
   3670      * If false, the text will be wrapped to the width of the View.
   3671      *
   3672      * @attr ref android.R.styleable#TextView_scrollHorizontally
   3673      * @hide
   3674      */
   3675     public boolean getHorizontallyScrolling() {
   3676         return mHorizontallyScrolling;
   3677     }
   3678 
   3679     /**
   3680      * Makes the TextView at least this many lines tall.
   3681      *
   3682      * Setting this value overrides any other (minimum) height setting. A single line TextView will
   3683      * set this value to 1.
   3684      *
   3685      * @see #getMinLines()
   3686      *
   3687      * @attr ref android.R.styleable#TextView_minLines
   3688      */
   3689     @android.view.RemotableViewMethod
   3690     public void setMinLines(int minlines) {
   3691         mMinimum = minlines;
   3692         mMinMode = LINES;
   3693 
   3694         requestLayout();
   3695         invalidate();
   3696     }
   3697 
   3698     /**
   3699      * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
   3700      * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
   3701      *
   3702      * @see #setMinLines(int)
   3703      *
   3704      * @attr ref android.R.styleable#TextView_minLines
   3705      */
   3706     public int getMinLines() {
   3707         return mMinMode == LINES ? mMinimum : -1;
   3708     }
   3709 
   3710     /**
   3711      * Makes the TextView at least this many pixels tall.
   3712      *
   3713      * Setting this value overrides any other (minimum) number of lines setting.
   3714      *
   3715      * @attr ref android.R.styleable#TextView_minHeight
   3716      */
   3717     @android.view.RemotableViewMethod
   3718     public void setMinHeight(int minHeight) {
   3719         mMinimum = minHeight;
   3720         mMinMode = PIXELS;
   3721 
   3722         requestLayout();
   3723         invalidate();
   3724     }
   3725 
   3726     /**
   3727      * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
   3728      * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
   3729      *
   3730      * @see #setMinHeight(int)
   3731      *
   3732      * @attr ref android.R.styleable#TextView_minHeight
   3733      */
   3734     public int getMinHeight() {
   3735         return mMinMode == PIXELS ? mMinimum : -1;
   3736     }
   3737 
   3738     /**
   3739      * Makes the TextView at most this many lines tall.
   3740      *
   3741      * Setting this value overrides any other (maximum) height setting.
   3742      *
   3743      * @attr ref android.R.styleable#TextView_maxLines
   3744      */
   3745     @android.view.RemotableViewMethod
   3746     public void setMaxLines(int maxlines) {
   3747         mMaximum = maxlines;
   3748         mMaxMode = LINES;
   3749 
   3750         requestLayout();
   3751         invalidate();
   3752     }
   3753 
   3754     /**
   3755      * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
   3756      * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
   3757      *
   3758      * @see #setMaxLines(int)
   3759      *
   3760      * @attr ref android.R.styleable#TextView_maxLines
   3761      */
   3762     public int getMaxLines() {
   3763         return mMaxMode == LINES ? mMaximum : -1;
   3764     }
   3765 
   3766     /**
   3767      * Makes the TextView at most this many pixels tall.  This option is mutually exclusive with the
   3768      * {@link #setMaxLines(int)} method.
   3769      *
   3770      * Setting this value overrides any other (maximum) number of lines setting.
   3771      *
   3772      * @attr ref android.R.styleable#TextView_maxHeight
   3773      */
   3774     @android.view.RemotableViewMethod
   3775     public void setMaxHeight(int maxHeight) {
   3776         mMaximum = maxHeight;
   3777         mMaxMode = PIXELS;
   3778 
   3779         requestLayout();
   3780         invalidate();
   3781     }
   3782 
   3783     /**
   3784      * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
   3785      * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
   3786      *
   3787      * @see #setMaxHeight(int)
   3788      *
   3789      * @attr ref android.R.styleable#TextView_maxHeight
   3790      */
   3791     public int getMaxHeight() {
   3792         return mMaxMode == PIXELS ? mMaximum : -1;
   3793     }
   3794 
   3795     /**
   3796      * Makes the TextView exactly this many lines tall.
   3797      *
   3798      * Note that setting this value overrides any other (minimum / maximum) number of lines or
   3799      * height setting. A single line TextView will set this value to 1.
   3800      *
   3801      * @attr ref android.R.styleable#TextView_lines
   3802      */
   3803     @android.view.RemotableViewMethod
   3804     public void setLines(int lines) {
   3805         mMaximum = mMinimum = lines;
   3806         mMaxMode = mMinMode = LINES;
   3807 
   3808         requestLayout();
   3809         invalidate();
   3810     }
   3811 
   3812     /**
   3813      * Makes the TextView exactly this many pixels tall.
   3814      * You could do the same thing by specifying this number in the
   3815      * LayoutParams.
   3816      *
   3817      * Note that setting this value overrides any other (minimum / maximum) number of lines or
   3818      * height setting.
   3819      *
   3820      * @attr ref android.R.styleable#TextView_height
   3821      */
   3822     @android.view.RemotableViewMethod
   3823     public void setHeight(int pixels) {
   3824         mMaximum = mMinimum = pixels;
   3825         mMaxMode = mMinMode = PIXELS;
   3826 
   3827         requestLayout();
   3828         invalidate();
   3829     }
   3830 
   3831     /**
   3832      * Makes the TextView at least this many ems wide
   3833      *
   3834      * @attr ref android.R.styleable#TextView_minEms
   3835      */
   3836     @android.view.RemotableViewMethod
   3837     public void setMinEms(int minems) {
   3838         mMinWidth = minems;
   3839         mMinWidthMode = EMS;
   3840 
   3841         requestLayout();
   3842         invalidate();
   3843     }
   3844 
   3845     /**
   3846      * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
   3847      * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
   3848      *
   3849      * @see #setMinEms(int)
   3850      * @see #setEms(int)
   3851      *
   3852      * @attr ref android.R.styleable#TextView_minEms
   3853      */
   3854     public int getMinEms() {
   3855         return mMinWidthMode == EMS ? mMinWidth : -1;
   3856     }
   3857 
   3858     /**
   3859      * Makes the TextView at least this many pixels wide
   3860      *
   3861      * @attr ref android.R.styleable#TextView_minWidth
   3862      */
   3863     @android.view.RemotableViewMethod
   3864     public void setMinWidth(int minpixels) {
   3865         mMinWidth = minpixels;
   3866         mMinWidthMode = PIXELS;
   3867 
   3868         requestLayout();
   3869         invalidate();
   3870     }
   3871 
   3872     /**
   3873      * @return the minimum width of the TextView, in pixels or -1 if the minimum width
   3874      * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
   3875      *
   3876      * @see #setMinWidth(int)
   3877      * @see #setWidth(int)
   3878      *
   3879      * @attr ref android.R.styleable#TextView_minWidth
   3880      */
   3881     public int getMinWidth() {
   3882         return mMinWidthMode == PIXELS ? mMinWidth : -1;
   3883     }
   3884 
   3885     /**
   3886      * Makes the TextView at most this many ems wide
   3887      *
   3888      * @attr ref android.R.styleable#TextView_maxEms
   3889      */
   3890     @android.view.RemotableViewMethod
   3891     public void setMaxEms(int maxems) {
   3892         mMaxWidth = maxems;
   3893         mMaxWidthMode = EMS;
   3894 
   3895         requestLayout();
   3896         invalidate();
   3897     }
   3898 
   3899     /**
   3900      * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
   3901      * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
   3902      *
   3903      * @see #setMaxEms(int)
   3904      * @see #setEms(int)
   3905      *
   3906      * @attr ref android.R.styleable#TextView_maxEms
   3907      */
   3908     public int getMaxEms() {
   3909         return mMaxWidthMode == EMS ? mMaxWidth : -1;
   3910     }
   3911 
   3912     /**
   3913      * Makes the TextView at most this many pixels wide
   3914      *
   3915      * @attr ref android.R.styleable#TextView_maxWidth
   3916      */
   3917     @android.view.RemotableViewMethod
   3918     public void setMaxWidth(int maxpixels) {
   3919         mMaxWidth = maxpixels;
   3920         mMaxWidthMode = PIXELS;
   3921 
   3922         requestLayout();
   3923         invalidate();
   3924     }
   3925 
   3926     /**
   3927      * @return the maximum width of the TextView, in pixels or -1 if the maximum width
   3928      * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
   3929      *
   3930      * @see #setMaxWidth(int)
   3931      * @see #setWidth(int)
   3932      *
   3933      * @attr ref android.R.styleable#TextView_maxWidth
   3934      */
   3935     public int getMaxWidth() {
   3936         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
   3937     }
   3938 
   3939     /**
   3940      * Makes the TextView exactly this many ems wide
   3941      *
   3942      * @see #setMaxEms(int)
   3943      * @see #setMinEms(int)
   3944      * @see #getMinEms()
   3945      * @see #getMaxEms()
   3946      *
   3947      * @attr ref android.R.styleable#TextView_ems
   3948      */
   3949     @android.view.RemotableViewMethod
   3950     public void setEms(int ems) {
   3951         mMaxWidth = mMinWidth = ems;
   3952         mMaxWidthMode = mMinWidthMode = EMS;
   3953 
   3954         requestLayout();
   3955         invalidate();
   3956     }
   3957 
   3958     /**
   3959      * Makes the TextView exactly this many pixels wide.
   3960      * You could do the same thing by specifying this number in the
   3961      * LayoutParams.
   3962      *
   3963      * @see #setMaxWidth(int)
   3964      * @see #setMinWidth(int)
   3965      * @see #getMinWidth()
   3966      * @see #getMaxWidth()
   3967      *
   3968      * @attr ref android.R.styleable#TextView_width
   3969      */
   3970     @android.view.RemotableViewMethod
   3971     public void setWidth(int pixels) {
   3972         mMaxWidth = mMinWidth = pixels;
   3973         mMaxWidthMode = mMinWidthMode = PIXELS;
   3974 
   3975         requestLayout();
   3976         invalidate();
   3977     }
   3978 
   3979     /**
   3980      * Sets line spacing for this TextView.  Each line will have its height
   3981      * multiplied by <code>mult</code> and have <code>add</code> added to it.
   3982      *
   3983      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   3984      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   3985      */
   3986     public void setLineSpacing(float add, float mult) {
   3987         if (mSpacingAdd != add || mSpacingMult != mult) {
   3988             mSpacingAdd = add;
   3989             mSpacingMult = mult;
   3990 
   3991             if (mLayout != null) {
   3992                 nullLayouts();
   3993                 requestLayout();
   3994                 invalidate();
   3995             }
   3996         }
   3997     }
   3998 
   3999     /**
   4000      * Gets the line spacing multiplier
   4001      *
   4002      * @return the value by which each line's height is multiplied to get its actual height.
   4003      *
   4004      * @see #setLineSpacing(float, float)
   4005      * @see #getLineSpacingExtra()
   4006      *
   4007      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   4008      */
   4009     public float getLineSpacingMultiplier() {
   4010         return mSpacingMult;
   4011     }
   4012 
   4013     /**
   4014      * Gets the line spacing extra space
   4015      *
   4016      * @return the extra space that is added to the height of each lines of this TextView.
   4017      *
   4018      * @see #setLineSpacing(float, float)
   4019      * @see #getLineSpacingMultiplier()
   4020      *
   4021      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   4022      */
   4023     public float getLineSpacingExtra() {
   4024         return mSpacingAdd;
   4025     }
   4026 
   4027     /**
   4028      * Convenience method: Append the specified text to the TextView's
   4029      * display buffer, upgrading it to BufferType.EDITABLE if it was
   4030      * not already editable.
   4031      */
   4032     public final void append(CharSequence text) {
   4033         append(text, 0, text.length());
   4034     }
   4035 
   4036     /**
   4037      * Convenience method: Append the specified text slice to the TextView's
   4038      * display buffer, upgrading it to BufferType.EDITABLE if it was
   4039      * not already editable.
   4040      */
   4041     public void append(CharSequence text, int start, int end) {
   4042         if (!(mText instanceof Editable)) {
   4043             setText(mText, BufferType.EDITABLE);
   4044         }
   4045 
   4046         ((Editable) mText).append(text, start, end);
   4047 
   4048         if (mAutoLinkMask != 0) {
   4049             boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
   4050             // Do not change the movement method for text that support text selection as it
   4051             // would prevent an arbitrary cursor displacement.
   4052             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
   4053                 setMovementMethod(LinkMovementMethod.getInstance());
   4054             }
   4055         }
   4056     }
   4057 
   4058     private void updateTextColors() {
   4059         boolean inval = false;
   4060         int color = mTextColor.getColorForState(getDrawableState(), 0);
   4061         if (color != mCurTextColor) {
   4062             mCurTextColor = color;
   4063             inval = true;
   4064         }
   4065         if (mLinkTextColor != null) {
   4066             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
   4067             if (color != mTextPaint.linkColor) {
   4068                 mTextPaint.linkColor = color;
   4069                 inval = true;
   4070             }
   4071         }
   4072         if (mHintTextColor != null) {
   4073             color = mHintTextColor.getColorForState(getDrawableState(), 0);
   4074             if (color != mCurHintTextColor) {
   4075                 mCurHintTextColor = color;
   4076                 if (mText.length() == 0) {
   4077                     inval = true;
   4078                 }
   4079             }
   4080         }
   4081         if (inval) {
   4082             // Text needs to be redrawn with the new color
   4083             if (mEditor != null) mEditor.invalidateTextDisplayList();
   4084             invalidate();
   4085         }
   4086     }
   4087 
   4088     @Override
   4089     protected void drawableStateChanged() {
   4090         super.drawableStateChanged();
   4091 
   4092         if (mTextColor != null && mTextColor.isStateful()
   4093                 || (mHintTextColor != null && mHintTextColor.isStateful())
   4094                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
   4095             updateTextColors();
   4096         }
   4097 
   4098         if (mDrawables != null) {
   4099             final int[] state = getDrawableState();
   4100             for (Drawable dr : mDrawables.mShowing) {
   4101                 if (dr != null && dr.isStateful() && dr.setState(state)) {
   4102                     invalidateDrawable(dr);
   4103                 }
   4104             }
   4105         }
   4106     }
   4107 
   4108     @Override
   4109     public void drawableHotspotChanged(float x, float y) {
   4110         super.drawableHotspotChanged(x, y);
   4111 
   4112         if (mDrawables != null) {
   4113             for (Drawable dr : mDrawables.mShowing) {
   4114                 if (dr != null) {
   4115                     dr.setHotspot(x, y);
   4116                 }
   4117             }
   4118         }
   4119     }
   4120 
   4121     @Override
   4122     public Parcelable onSaveInstanceState() {
   4123         Parcelable superState = super.onSaveInstanceState();
   4124 
   4125         // Save state if we are forced to
   4126         final boolean freezesText = getFreezesText();
   4127         boolean hasSelection = false;
   4128         int start = -1;
   4129         int end = -1;
   4130 
   4131         if (mText != null) {
   4132             start = getSelectionStart();
   4133             end = getSelectionEnd();
   4134             if (start >= 0 || end >= 0) {
   4135                 // Or save state if there is a selection
   4136                 hasSelection = true;
   4137             }
   4138         }
   4139 
   4140         if (freezesText || hasSelection) {
   4141             SavedState ss = new SavedState(superState);
   4142 
   4143             if (freezesText) {
   4144                 if (mText instanceof Spanned) {
   4145                     final Spannable sp = new SpannableStringBuilder(mText);
   4146 
   4147                     if (mEditor != null) {
   4148                         removeMisspelledSpans(sp);
   4149                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
   4150                     }
   4151 
   4152                     ss.text = sp;
   4153                 } else {
   4154                     ss.text = mText.toString();
   4155                 }
   4156             }
   4157 
   4158             if (hasSelection) {
   4159                 // XXX Should also save the current scroll position!
   4160                 ss.selStart = start;
   4161                 ss.selEnd = end;
   4162             }
   4163 
   4164             if (isFocused() && start >= 0 && end >= 0) {
   4165                 ss.frozenWithFocus = true;
   4166             }
   4167 
   4168             ss.error = getError();
   4169 
   4170             if (mEditor != null) {
   4171                 ss.editorState = mEditor.saveInstanceState();
   4172             }
   4173             return ss;
   4174         }
   4175 
   4176         return superState;
   4177     }
   4178 
   4179     void removeMisspelledSpans(Spannable spannable) {
   4180         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
   4181                 SuggestionSpan.class);
   4182         for (int i = 0; i < suggestionSpans.length; i++) {
   4183             int flags = suggestionSpans[i].getFlags();
   4184             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
   4185                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
   4186                 spannable.removeSpan(suggestionSpans[i]);
   4187             }
   4188         }
   4189     }
   4190 
   4191     @Override
   4192     public void onRestoreInstanceState(Parcelable state) {
   4193         if (!(state instanceof SavedState)) {
   4194             super.onRestoreInstanceState(state);
   4195             return;
   4196         }
   4197 
   4198         SavedState ss = (SavedState)state;
   4199         super.onRestoreInstanceState(ss.getSuperState());
   4200 
   4201         // XXX restore buffer type too, as well as lots of other stuff
   4202         if (ss.text != null) {
   4203             setText(ss.text);
   4204         }
   4205 
   4206         if (ss.selStart >= 0 && ss.selEnd >= 0) {
   4207             if (mText instanceof Spannable) {
   4208                 int len = mText.length();
   4209 
   4210                 if (ss.selStart > len || ss.selEnd > len) {
   4211                     String restored = "";
   4212 
   4213                     if (ss.text != null) {
   4214                         restored = "(restored) ";
   4215                     }
   4216 
   4217                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
   4218                           "/" + ss.selEnd + " out of range for " + restored +
   4219                           "text " + mText);
   4220                 } else {
   4221                     Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
   4222 
   4223                     if (ss.frozenWithFocus) {
   4224                         createEditorIfNeeded();
   4225                         mEditor.mFrozenWithFocus = true;
   4226                     }
   4227                 }
   4228             }
   4229         }
   4230 
   4231         if (ss.error != null) {
   4232             final CharSequence error = ss.error;
   4233             // Display the error later, after the first layout pass
   4234             post(new Runnable() {
   4235                 public void run() {
   4236                     if (mEditor == null || !mEditor.mErrorWasChanged) {
   4237                         setError(error);
   4238                     }
   4239                 }
   4240             });
   4241         }
   4242 
   4243         if (ss.editorState != null) {
   4244             createEditorIfNeeded();
   4245             mEditor.restoreInstanceState(ss.editorState);
   4246         }
   4247     }
   4248 
   4249     /**
   4250      * Control whether this text view saves its entire text contents when
   4251      * freezing to an icicle, in addition to dynamic state such as cursor
   4252      * position.  By default this is false, not saving the text.  Set to true
   4253      * if the text in the text view is not being saved somewhere else in
   4254      * persistent storage (such as in a content provider) so that if the
   4255      * view is later thawed the user will not lose their data. For
   4256      * {@link android.widget.EditText} it is always enabled, regardless of
   4257      * the value of the attribute.
   4258      *
   4259      * @param freezesText Controls whether a frozen icicle should include the
   4260      * entire text data: true to include it, false to not.
   4261      *
   4262      * @attr ref android.R.styleable#TextView_freezesText
   4263      */
   4264     @android.view.RemotableViewMethod
   4265     public void setFreezesText(boolean freezesText) {
   4266         mFreezesText = freezesText;
   4267     }
   4268 
   4269     /**
   4270      * Return whether this text view is including its entire text contents
   4271      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
   4272      *
   4273      * @return Returns true if text is included, false if it isn't.
   4274      *
   4275      * @see #setFreezesText
   4276      */
   4277     public boolean getFreezesText() {
   4278         return mFreezesText;
   4279     }
   4280 
   4281     ///////////////////////////////////////////////////////////////////////////
   4282 
   4283     /**
   4284      * Sets the Factory used to create new Editables.
   4285      */
   4286     public final void setEditableFactory(Editable.Factory factory) {
   4287         mEditableFactory = factory;
   4288         setText(mText);
   4289     }
   4290 
   4291     /**
   4292      * Sets the Factory used to create new Spannables.
   4293      */
   4294     public final void setSpannableFactory(Spannable.Factory factory) {
   4295         mSpannableFactory = factory;
   4296         setText(mText);
   4297     }
   4298 
   4299     /**
   4300      * Sets the string value of the TextView. TextView <em>does not</em> accept
   4301      * HTML-like formatting, which you can do with text strings in XML resource files.
   4302      * To style your strings, attach android.text.style.* objects to a
   4303      * {@link android.text.SpannableString SpannableString}, or see the
   4304      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
   4305      * Available Resource Types</a> documentation for an example of setting
   4306      * formatted text in the XML resource file.
   4307      *
   4308      * @attr ref android.R.styleable#TextView_text
   4309      */
   4310     @android.view.RemotableViewMethod
   4311     public final void setText(CharSequence text) {
   4312         setText(text, mBufferType);
   4313     }
   4314 
   4315     /**
   4316      * Like {@link #setText(CharSequence)},
   4317      * except that the cursor position (if any) is retained in the new text.
   4318      *
   4319      * @param text The new text to place in the text view.
   4320      *
   4321      * @see #setText(CharSequence)
   4322      */
   4323     @android.view.RemotableViewMethod
   4324     public final void setTextKeepState(CharSequence text) {
   4325         setTextKeepState(text, mBufferType);
   4326     }
   4327 
   4328     /**
   4329      * Sets the text that this TextView is to display (see
   4330      * {@link #setText(CharSequence)}) and also sets whether it is stored
   4331      * in a styleable/spannable buffer and whether it is editable.
   4332      *
   4333      * @attr ref android.R.styleable#TextView_text
   4334      * @attr ref android.R.styleable#TextView_bufferType
   4335      */
   4336     public void setText(CharSequence text, BufferType type) {
   4337         setText(text, type, true, 0);
   4338 
   4339         if (mCharWrapper != null) {
   4340             mCharWrapper.mChars = null;
   4341         }
   4342     }
   4343 
   4344     private void setText(CharSequence text, BufferType type,
   4345                          boolean notifyBefore, int oldlen) {
   4346         if (text == null) {
   4347             text = "";
   4348         }
   4349 
   4350         // If suggestions are not enabled, remove the suggestion spans from the text
   4351         if (!isSuggestionsEnabled()) {
   4352             text = removeSuggestionSpans(text);
   4353         }
   4354 
   4355         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
   4356 
   4357         if (text instanceof Spanned &&
   4358             ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
   4359             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
   4360                 setHorizontalFadingEdgeEnabled(true);
   4361                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
   4362             } else {
   4363                 setHorizontalFadingEdgeEnabled(false);
   4364                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   4365             }
   4366             setEllipsize(TextUtils.TruncateAt.MARQUEE);
   4367         }
   4368 
   4369         int n = mFilters.length;
   4370         for (int i = 0; i < n; i++) {
   4371             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
   4372             if (out != null) {
   4373                 text = out;
   4374             }
   4375         }
   4376 
   4377         if (notifyBefore) {
   4378             if (mText != null) {
   4379                 oldlen = mText.length();
   4380                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
   4381             } else {
   4382                 sendBeforeTextChanged("", 0, 0, text.length());
   4383             }
   4384         }
   4385 
   4386         boolean needEditableForNotification = false;
   4387 
   4388         if (mListeners != null && mListeners.size() != 0) {
   4389             needEditableForNotification = true;
   4390         }
   4391 
   4392         if (type == BufferType.EDITABLE || getKeyListener() != null ||
   4393                 needEditableForNotification) {
   4394             createEditorIfNeeded();
   4395             mEditor.forgetUndoRedo();
   4396             Editable t = mEditableFactory.newEditable(text);
   4397             text = t;
   4398             setFilters(t, mFilters);
   4399             InputMethodManager imm = InputMethodManager.peekInstance();
   4400             if (imm != null) imm.restartInput(this);
   4401         } else if (type == BufferType.SPANNABLE || mMovement != null) {
   4402             text = mSpannableFactory.newSpannable(text);
   4403         } else if (!(text instanceof CharWrapper)) {
   4404             text = TextUtils.stringOrSpannedString(text);
   4405         }
   4406 
   4407         if (mAutoLinkMask != 0) {
   4408             Spannable s2;
   4409 
   4410             if (type == BufferType.EDITABLE || text instanceof Spannable) {
   4411                 s2 = (Spannable) text;
   4412             } else {
   4413                 s2 = mSpannableFactory.newSpannable(text);
   4414             }
   4415 
   4416             if (Linkify.addLinks(s2, mAutoLinkMask)) {
   4417                 text = s2;
   4418                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
   4419 
   4420                 /*
   4421                  * We must go ahead and set the text before changing the
   4422                  * movement method, because setMovementMethod() may call
   4423                  * setText() again to try to upgrade the buffer type.
   4424                  */
   4425                 mText = text;
   4426 
   4427                 // Do not change the movement method for text that support text selection as it
   4428                 // would prevent an arbitrary cursor displacement.
   4429                 if (mLinksClickable && !textCanBeSelected()) {
   4430                     setMovementMethod(LinkMovementMethod.getInstance());
   4431                 }
   4432             }
   4433         }
   4434 
   4435         mBufferType = type;
   4436         mText = text;
   4437 
   4438         if (mTransformation == null) {
   4439             mTransformed = text;
   4440         } else {
   4441             mTransformed = mTransformation.getTransformation(text, this);
   4442         }
   4443 
   4444         final int textLength = text.length();
   4445 
   4446         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
   4447             Spannable sp = (Spannable) text;
   4448 
   4449             // Remove any ChangeWatchers that might have come from other TextViews.
   4450             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
   4451             final int count = watchers.length;
   4452             for (int i = 0; i < count; i++) {
   4453                 sp.removeSpan(watchers[i]);
   4454             }
   4455 
   4456             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
   4457 
   4458             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
   4459                        (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
   4460 
   4461             if (mEditor != null) mEditor.addSpanWatchers(sp);
   4462 
   4463             if (mTransformation != null) {
   4464                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   4465             }
   4466 
   4467             if (mMovement != null) {
   4468                 mMovement.initialize(this, (Spannable) text);
   4469 
   4470                 /*
   4471                  * Initializing the movement method will have set the
   4472                  * selection, so reset mSelectionMoved to keep that from
   4473                  * interfering with the normal on-focus selection-setting.
   4474                  */
   4475                 if (mEditor != null) mEditor.mSelectionMoved = false;
   4476             }
   4477         }
   4478 
   4479         if (mLayout != null) {
   4480             checkForRelayout();
   4481         }
   4482 
   4483         sendOnTextChanged(text, 0, oldlen, textLength);
   4484         onTextChanged(text, 0, oldlen, textLength);
   4485 
   4486         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
   4487 
   4488         if (needEditableForNotification) {
   4489             sendAfterTextChanged((Editable) text);
   4490         }
   4491 
   4492         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
   4493         if (mEditor != null) mEditor.prepareCursorControllers();
   4494     }
   4495 
   4496     /**
   4497      * Sets the TextView to display the specified slice of the specified
   4498      * char array.  You must promise that you will not change the contents
   4499      * of the array except for right before another call to setText(),
   4500      * since the TextView has no way to know that the text
   4501      * has changed and that it needs to invalidate and re-layout.
   4502      */
   4503     public final void setText(char[] text, int start, int len) {
   4504         int oldlen = 0;
   4505 
   4506         if (start < 0 || len < 0 || start + len > text.length) {
   4507             throw new IndexOutOfBoundsException(start + ", " + len);
   4508         }
   4509 
   4510         /*
   4511          * We must do the before-notification here ourselves because if
   4512          * the old text is a CharWrapper we destroy it before calling
   4513          * into the normal path.
   4514          */
   4515         if (mText != null) {
   4516             oldlen = mText.length();
   4517             sendBeforeTextChanged(mText, 0, oldlen, len);
   4518         } else {
   4519             sendBeforeTextChanged("", 0, 0, len);
   4520         }
   4521 
   4522         if (mCharWrapper == null) {
   4523             mCharWrapper = new CharWrapper(text, start, len);
   4524         } else {
   4525             mCharWrapper.set(text, start, len);
   4526         }
   4527 
   4528         setText(mCharWrapper, mBufferType, false, oldlen);
   4529     }
   4530 
   4531     /**
   4532      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
   4533      * except that the cursor position (if any) is retained in the new text.
   4534      *
   4535      * @see #setText(CharSequence, android.widget.TextView.BufferType)
   4536      */
   4537     public final void setTextKeepState(CharSequence text, BufferType type) {
   4538         int start = getSelectionStart();
   4539         int end = getSelectionEnd();
   4540         int len = text.length();
   4541 
   4542         setText(text, type);
   4543 
   4544         if (start >= 0 || end >= 0) {
   4545             if (mText instanceof Spannable) {
   4546                 Selection.setSelection((Spannable) mText,
   4547                                        Math.max(0, Math.min(start, len)),
   4548                                        Math.max(0, Math.min(end, len)));
   4549             }
   4550         }
   4551     }
   4552 
   4553     @android.view.RemotableViewMethod
   4554     public final void setText(@StringRes int resid) {
   4555         setText(getContext().getResources().getText(resid));
   4556     }
   4557 
   4558     public final void setText(@StringRes int resid, BufferType type) {
   4559         setText(getContext().getResources().getText(resid), type);
   4560     }
   4561 
   4562     /**
   4563      * Sets the text to be displayed when the text of the TextView is empty.
   4564      * Null means to use the normal empty text. The hint does not currently
   4565      * participate in determining the size of the view.
   4566      *
   4567      * @attr ref android.R.styleable#TextView_hint
   4568      */
   4569     @android.view.RemotableViewMethod
   4570     public final void setHint(CharSequence hint) {
   4571         mHint = TextUtils.stringOrSpannedString(hint);
   4572 
   4573         if (mLayout != null) {
   4574             checkForRelayout();
   4575         }
   4576 
   4577         if (mText.length() == 0) {
   4578             invalidate();
   4579         }
   4580 
   4581         // Invalidate display list if hint is currently used
   4582         if (mEditor != null && mText.length() == 0 && mHint != null) {
   4583             mEditor.invalidateTextDisplayList();
   4584         }
   4585     }
   4586 
   4587     /**
   4588      * Sets the text to be displayed when the text of the TextView is empty,
   4589      * from a resource.
   4590      *
   4591      * @attr ref android.R.styleable#TextView_hint
   4592      */
   4593     @android.view.RemotableViewMethod
   4594     public final void setHint(@StringRes int resid) {
   4595         setHint(getContext().getResources().getText(resid));
   4596     }
   4597 
   4598     /**
   4599      * Returns the hint that is displayed when the text of the TextView
   4600      * is empty.
   4601      *
   4602      * @attr ref android.R.styleable#TextView_hint
   4603      */
   4604     @ViewDebug.CapturedViewProperty
   4605     public CharSequence getHint() {
   4606         return mHint;
   4607     }
   4608 
   4609     boolean isSingleLine() {
   4610         return mSingleLine;
   4611     }
   4612 
   4613     private static boolean isMultilineInputType(int type) {
   4614         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
   4615             (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
   4616     }
   4617 
   4618     /**
   4619      * Removes the suggestion spans.
   4620      */
   4621     CharSequence removeSuggestionSpans(CharSequence text) {
   4622        if (text instanceof Spanned) {
   4623            Spannable spannable;
   4624            if (text instanceof Spannable) {
   4625                spannable = (Spannable) text;
   4626            } else {
   4627                spannable = new SpannableString(text);
   4628                text = spannable;
   4629            }
   4630 
   4631            SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
   4632            for (int i = 0; i < spans.length; i++) {
   4633                spannable.removeSpan(spans[i]);
   4634            }
   4635        }
   4636        return text;
   4637     }
   4638 
   4639     /**
   4640      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
   4641      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
   4642      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
   4643      * then a soft keyboard will not be displayed for this text view.
   4644      *
   4645      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
   4646      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
   4647      * type.
   4648      *
   4649      * @see #getInputType()
   4650      * @see #setRawInputType(int)
   4651      * @see android.text.InputType
   4652      * @attr ref android.R.styleable#TextView_inputType
   4653      */
   4654     public void setInputType(int type) {
   4655         final boolean wasPassword = isPasswordInputType(getInputType());
   4656         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
   4657         setInputType(type, false);
   4658         final boolean isPassword = isPasswordInputType(type);
   4659         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
   4660         boolean forceUpdate = false;
   4661         if (isPassword) {
   4662             setTransformationMethod(PasswordTransformationMethod.getInstance());
   4663             setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
   4664         } else if (isVisiblePassword) {
   4665             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   4666                 forceUpdate = true;
   4667             }
   4668             setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
   4669         } else if (wasPassword || wasVisiblePassword) {
   4670             // not in password mode, clean up typeface and transformation
   4671             setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
   4672             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   4673                 forceUpdate = true;
   4674             }
   4675         }
   4676 
   4677         boolean singleLine = !isMultilineInputType(type);
   4678 
   4679         // We need to update the single line mode if it has changed or we
   4680         // were previously in password mode.
   4681         if (mSingleLine != singleLine || forceUpdate) {
   4682             // Change single line mode, but only change the transformation if
   4683             // we are not in password mode.
   4684             applySingleLine(singleLine, !isPassword, true);
   4685         }
   4686 
   4687         if (!isSuggestionsEnabled()) {
   4688             mText = removeSuggestionSpans(mText);
   4689         }
   4690 
   4691         InputMethodManager imm = InputMethodManager.peekInstance();
   4692         if (imm != null) imm.restartInput(this);
   4693     }
   4694 
   4695     /**
   4696      * It would be better to rely on the input type for everything. A password inputType should have
   4697      * a password transformation. We should hence use isPasswordInputType instead of this method.
   4698      *
   4699      * We should:
   4700      * - Call setInputType in setKeyListener instead of changing the input type directly (which
   4701      * would install the correct transformation).
   4702      * - Refuse the installation of a non-password transformation in setTransformation if the input
   4703      * type is password.
   4704      *
   4705      * However, this is like this for legacy reasons and we cannot break existing apps. This method
   4706      * is useful since it matches what the user can see (obfuscated text or not).
   4707      *
   4708      * @return true if the current transformation method is of the password type.
   4709      */
   4710     boolean hasPasswordTransformationMethod() {
   4711         return mTransformation instanceof PasswordTransformationMethod;
   4712     }
   4713 
   4714     private static boolean isPasswordInputType(int inputType) {
   4715         final int variation =
   4716                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
   4717         return variation
   4718                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
   4719                 || variation
   4720                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
   4721                 || variation
   4722                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
   4723     }
   4724 
   4725     private static boolean isVisiblePasswordInputType(int inputType) {
   4726         final int variation =
   4727                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
   4728         return variation
   4729                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
   4730     }
   4731 
   4732     /**
   4733      * Directly change the content type integer of the text view, without
   4734      * modifying any other state.
   4735      * @see #setInputType(int)
   4736      * @see android.text.InputType
   4737      * @attr ref android.R.styleable#TextView_inputType
   4738      */
   4739     public void setRawInputType(int type) {
   4740         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
   4741         createEditorIfNeeded();
   4742         mEditor.mInputType = type;
   4743     }
   4744 
   4745     private void setInputType(int type, boolean direct) {
   4746         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
   4747         KeyListener input;
   4748         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
   4749             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
   4750             TextKeyListener.Capitalize cap;
   4751             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
   4752                 cap = TextKeyListener.Capitalize.CHARACTERS;
   4753             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
   4754                 cap = TextKeyListener.Capitalize.WORDS;
   4755             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
   4756                 cap = TextKeyListener.Capitalize.SENTENCES;
   4757             } else {
   4758                 cap = TextKeyListener.Capitalize.NONE;
   4759             }
   4760             input = TextKeyListener.getInstance(autotext, cap);
   4761         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
   4762             input = DigitsKeyListener.getInstance(
   4763                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
   4764                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
   4765         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
   4766             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
   4767                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
   4768                     input = DateKeyListener.getInstance();
   4769                     break;
   4770                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
   4771                     input = TimeKeyListener.getInstance();
   4772                     break;
   4773                 default:
   4774                     input = DateTimeKeyListener.getInstance();
   4775                     break;
   4776             }
   4777         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
   4778             input = DialerKeyListener.getInstance();
   4779         } else {
   4780             input = TextKeyListener.getInstance();
   4781         }
   4782         setRawInputType(type);
   4783         if (direct) {
   4784             createEditorIfNeeded();
   4785             mEditor.mKeyListener = input;
   4786         } else {
   4787             setKeyListenerOnly(input);
   4788         }
   4789     }
   4790 
   4791     /**
   4792      * Get the type of the editable content.
   4793      *
   4794      * @see #setInputType(int)
   4795      * @see android.text.InputType
   4796      */
   4797     public int getInputType() {
   4798         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
   4799     }
   4800 
   4801     /**
   4802      * Change the editor type integer associated with the text view, which
   4803      * will be reported to an IME with {@link EditorInfo#imeOptions} when it
   4804      * has focus.
   4805      * @see #getImeOptions
   4806      * @see android.view.inputmethod.EditorInfo
   4807      * @attr ref android.R.styleable#TextView_imeOptions
   4808      */
   4809     public void setImeOptions(int imeOptions) {
   4810         createEditorIfNeeded();
   4811         mEditor.createInputContentTypeIfNeeded();
   4812         mEditor.mInputContentType.imeOptions = imeOptions;
   4813     }
   4814 
   4815     /**
   4816      * Get the type of the IME editor.
   4817      *
   4818      * @see #setImeOptions(int)
   4819      * @see android.view.inputmethod.EditorInfo
   4820      */
   4821     public int getImeOptions() {
   4822         return mEditor != null && mEditor.mInputContentType != null
   4823                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
   4824     }
   4825 
   4826     /**
   4827      * Change the custom IME action associated with the text view, which
   4828      * will be reported to an IME with {@link EditorInfo#actionLabel}
   4829      * and {@link EditorInfo#actionId} when it has focus.
   4830      * @see #getImeActionLabel
   4831      * @see #getImeActionId
   4832      * @see android.view.inputmethod.EditorInfo
   4833      * @attr ref android.R.styleable#TextView_imeActionLabel
   4834      * @attr ref android.R.styleable#TextView_imeActionId
   4835      */
   4836     public void setImeActionLabel(CharSequence label, int actionId) {
   4837         createEditorIfNeeded();
   4838         mEditor.createInputContentTypeIfNeeded();
   4839         mEditor.mInputContentType.imeActionLabel = label;
   4840         mEditor.mInputContentType.imeActionId = actionId;
   4841     }
   4842 
   4843     /**
   4844      * Get the IME action label previous set with {@link #setImeActionLabel}.
   4845      *
   4846      * @see #setImeActionLabel
   4847      * @see android.view.inputmethod.EditorInfo
   4848      */
   4849     public CharSequence getImeActionLabel() {
   4850         return mEditor != null && mEditor.mInputContentType != null
   4851                 ? mEditor.mInputContentType.imeActionLabel : null;
   4852     }
   4853 
   4854     /**
   4855      * Get the IME action ID previous set with {@link #setImeActionLabel}.
   4856      *
   4857      * @see #setImeActionLabel
   4858      * @see android.view.inputmethod.EditorInfo
   4859      */
   4860     public int getImeActionId() {
   4861         return mEditor != null && mEditor.mInputContentType != null
   4862                 ? mEditor.mInputContentType.imeActionId : 0;
   4863     }
   4864 
   4865     /**
   4866      * Set a special listener to be called when an action is performed
   4867      * on the text view.  This will be called when the enter key is pressed,
   4868      * or when an action supplied to the IME is selected by the user.  Setting
   4869      * this means that the normal hard key event will not insert a newline
   4870      * into the text view, even if it is multi-line; holding down the ALT
   4871      * modifier will, however, allow the user to insert a newline character.
   4872      */
   4873     public void setOnEditorActionListener(OnEditorActionListener l) {
   4874         createEditorIfNeeded();
   4875         mEditor.createInputContentTypeIfNeeded();
   4876         mEditor.mInputContentType.onEditorActionListener = l;
   4877     }
   4878 
   4879     /**
   4880      * Called when an attached input method calls
   4881      * {@link InputConnection#performEditorAction(int)
   4882      * InputConnection.performEditorAction()}
   4883      * for this text view.  The default implementation will call your action
   4884      * listener supplied to {@link #setOnEditorActionListener}, or perform
   4885      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
   4886      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
   4887      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
   4888      * EditorInfo.IME_ACTION_DONE}.
   4889      *
   4890      * <p>For backwards compatibility, if no IME options have been set and the
   4891      * text view would not normally advance focus on enter, then
   4892      * the NEXT and DONE actions received here will be turned into an enter
   4893      * key down/up pair to go through the normal key handling.
   4894      *
   4895      * @param actionCode The code of the action being performed.
   4896      *
   4897      * @see #setOnEditorActionListener
   4898      */
   4899     public void onEditorAction(int actionCode) {
   4900         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
   4901         if (ict != null) {
   4902             if (ict.onEditorActionListener != null) {
   4903                 if (ict.onEditorActionListener.onEditorAction(this,
   4904                         actionCode, null)) {
   4905                     return;
   4906                 }
   4907             }
   4908 
   4909             // This is the handling for some default action.
   4910             // Note that for backwards compatibility we don't do this
   4911             // default handling if explicit ime options have not been given,
   4912             // instead turning this into the normal enter key codes that an
   4913             // app may be expecting.
   4914             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
   4915                 View v = focusSearch(FOCUS_FORWARD);
   4916                 if (v != null) {
   4917                     if (!v.requestFocus(FOCUS_FORWARD)) {
   4918                         throw new IllegalStateException("focus search returned a view " +
   4919                                 "that wasn't able to take focus!");
   4920                     }
   4921                 }
   4922                 return;
   4923 
   4924             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
   4925                 View v = focusSearch(FOCUS_BACKWARD);
   4926                 if (v != null) {
   4927                     if (!v.requestFocus(FOCUS_BACKWARD)) {
   4928                         throw new IllegalStateException("focus search returned a view " +
   4929                                 "that wasn't able to take focus!");
   4930                     }
   4931                 }
   4932                 return;
   4933 
   4934             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
   4935                 InputMethodManager imm = InputMethodManager.peekInstance();
   4936                 if (imm != null && imm.isActive(this)) {
   4937                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   4938                 }
   4939                 return;
   4940             }
   4941         }
   4942 
   4943         ViewRootImpl viewRootImpl = getViewRootImpl();
   4944         if (viewRootImpl != null) {
   4945             long eventTime = SystemClock.uptimeMillis();
   4946             viewRootImpl.dispatchKeyFromIme(
   4947                     new KeyEvent(eventTime, eventTime,
   4948                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
   4949                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
   4950                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   4951                     | KeyEvent.FLAG_EDITOR_ACTION));
   4952             viewRootImpl.dispatchKeyFromIme(
   4953                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
   4954                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
   4955                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
   4956                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   4957                     | KeyEvent.FLAG_EDITOR_ACTION));
   4958         }
   4959     }
   4960 
   4961     /**
   4962      * Set the private content type of the text, which is the
   4963      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
   4964      * field that will be filled in when creating an input connection.
   4965      *
   4966      * @see #getPrivateImeOptions()
   4967      * @see EditorInfo#privateImeOptions
   4968      * @attr ref android.R.styleable#TextView_privateImeOptions
   4969      */
   4970     public void setPrivateImeOptions(String type) {
   4971         createEditorIfNeeded();
   4972         mEditor.createInputContentTypeIfNeeded();
   4973         mEditor.mInputContentType.privateImeOptions = type;
   4974     }
   4975 
   4976     /**
   4977      * Get the private type of the content.
   4978      *
   4979      * @see #setPrivateImeOptions(String)
   4980      * @see EditorInfo#privateImeOptions
   4981      */
   4982     public String getPrivateImeOptions() {
   4983         return mEditor != null && mEditor.mInputContentType != null
   4984                 ? mEditor.mInputContentType.privateImeOptions : null;
   4985     }
   4986 
   4987     /**
   4988      * Set the extra input data of the text, which is the
   4989      * {@link EditorInfo#extras TextBoxAttribute.extras}
   4990      * Bundle that will be filled in when creating an input connection.  The
   4991      * given integer is the resource ID of an XML resource holding an
   4992      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
   4993      *
   4994      * @see #getInputExtras(boolean)
   4995      * @see EditorInfo#extras
   4996      * @attr ref android.R.styleable#TextView_editorExtras
   4997      */
   4998     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
   4999         createEditorIfNeeded();
   5000         XmlResourceParser parser = getResources().getXml(xmlResId);
   5001         mEditor.createInputContentTypeIfNeeded();
   5002         mEditor.mInputContentType.extras = new Bundle();
   5003         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
   5004     }
   5005 
   5006     /**
   5007      * Retrieve the input extras currently associated with the text view, which
   5008      * can be viewed as well as modified.
   5009      *
   5010      * @param create If true, the extras will be created if they don't already
   5011      * exist.  Otherwise, null will be returned if none have been created.
   5012      * @see #setInputExtras(int)
   5013      * @see EditorInfo#extras
   5014      * @attr ref android.R.styleable#TextView_editorExtras
   5015      */
   5016     public Bundle getInputExtras(boolean create) {
   5017         if (mEditor == null && !create) return null;
   5018         createEditorIfNeeded();
   5019         if (mEditor.mInputContentType == null) {
   5020             if (!create) return null;
   5021             mEditor.createInputContentTypeIfNeeded();
   5022         }
   5023         if (mEditor.mInputContentType.extras == null) {
   5024             if (!create) return null;
   5025             mEditor.mInputContentType.extras = new Bundle();
   5026         }
   5027         return mEditor.mInputContentType.extras;
   5028     }
   5029 
   5030     /**
   5031      * Change "hint" locales associated with the text view, which will be reported to an IME with
   5032      * {@link EditorInfo#hintLocales} when it has focus.
   5033      *
   5034      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
   5035      * call {@link InputMethodManager#restartInput(View)}.</p>
   5036      * @param hintLocales List of the languages that the user is supposed to switch to no matter
   5037      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
   5038      * @see #getImeHIntLocales()
   5039      * @see android.view.inputmethod.EditorInfo#hintLocales
   5040      */
   5041     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
   5042         createEditorIfNeeded();
   5043         mEditor.createInputContentTypeIfNeeded();
   5044         mEditor.mInputContentType.imeHintLocales = hintLocales;
   5045     }
   5046 
   5047     /**
   5048      * @return The current languages list "hint". {@code null} when no "hint" is available.
   5049      * @see #setImeHintLocales(LocaleList)
   5050      * @see android.view.inputmethod.EditorInfo#hintLocales
   5051      */
   5052     @Nullable
   5053     public LocaleList getImeHintLocales() {
   5054         if (mEditor == null) { return null; }
   5055         if (mEditor.mInputContentType == null) { return null; }
   5056         return mEditor.mInputContentType.imeHintLocales;
   5057     }
   5058 
   5059     /**
   5060      * Returns the error message that was set to be displayed with
   5061      * {@link #setError}, or <code>null</code> if no error was set
   5062      * or if it the error was cleared by the widget after user input.
   5063      */
   5064     public CharSequence getError() {
   5065         return mEditor == null ? null : mEditor.mError;
   5066     }
   5067 
   5068     /**
   5069      * Sets the right-hand compound drawable of the TextView to the "error"
   5070      * icon and sets an error message that will be displayed in a popup when
   5071      * the TextView has focus.  The icon and error message will be reset to
   5072      * null when any key events cause changes to the TextView's text.  If the
   5073      * <code>error</code> is <code>null</code>, the error message and icon
   5074      * will be cleared.
   5075      */
   5076     @android.view.RemotableViewMethod
   5077     public void setError(CharSequence error) {
   5078         if (error == null) {
   5079             setError(null, null);
   5080         } else {
   5081             Drawable dr = getContext().getDrawable(
   5082                     com.android.internal.R.drawable.indicator_input_error);
   5083 
   5084             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
   5085             setError(error, dr);
   5086         }
   5087     }
   5088 
   5089     /**
   5090      * Sets the right-hand compound drawable of the TextView to the specified
   5091      * icon and sets an error message that will be displayed in a popup when
   5092      * the TextView has focus.  The icon and error message will be reset to
   5093      * null when any key events cause changes to the TextView's text.  The
   5094      * drawable must already have had {@link Drawable#setBounds} set on it.
   5095      * If the <code>error</code> is <code>null</code>, the error message will
   5096      * be cleared (and you should provide a <code>null</code> icon as well).
   5097      */
   5098     public void setError(CharSequence error, Drawable icon) {
   5099         createEditorIfNeeded();
   5100         mEditor.setError(error, icon);
   5101         notifyViewAccessibilityStateChangedIfNeeded(
   5102                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
   5103     }
   5104 
   5105     @Override
   5106     protected boolean setFrame(int l, int t, int r, int b) {
   5107         boolean result = super.setFrame(l, t, r, b);
   5108 
   5109         if (mEditor != null) mEditor.setFrame();
   5110 
   5111         restartMarqueeIfNeeded();
   5112 
   5113         return result;
   5114     }
   5115 
   5116     private void restartMarqueeIfNeeded() {
   5117         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   5118             mRestartMarquee = false;
   5119             startMarquee();
   5120         }
   5121     }
   5122 
   5123     /**
   5124      * Sets the list of input filters that will be used if the buffer is
   5125      * Editable. Has no effect otherwise.
   5126      *
   5127      * @attr ref android.R.styleable#TextView_maxLength
   5128      */
   5129     public void setFilters(InputFilter[] filters) {
   5130         if (filters == null) {
   5131             throw new IllegalArgumentException();
   5132         }
   5133 
   5134         mFilters = filters;
   5135 
   5136         if (mText instanceof Editable) {
   5137             setFilters((Editable) mText, filters);
   5138         }
   5139     }
   5140 
   5141     /**
   5142      * Sets the list of input filters on the specified Editable,
   5143      * and includes mInput in the list if it is an InputFilter.
   5144      */
   5145     private void setFilters(Editable e, InputFilter[] filters) {
   5146         if (mEditor != null) {
   5147             final boolean undoFilter = mEditor.mUndoInputFilter != null;
   5148             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
   5149             int num = 0;
   5150             if (undoFilter) num++;
   5151             if (keyFilter) num++;
   5152             if (num > 0) {
   5153                 InputFilter[] nf = new InputFilter[filters.length + num];
   5154 
   5155                 System.arraycopy(filters, 0, nf, 0, filters.length);
   5156                 num = 0;
   5157                 if (undoFilter) {
   5158                     nf[filters.length] = mEditor.mUndoInputFilter;
   5159                     num++;
   5160                 }
   5161                 if (keyFilter) {
   5162                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
   5163                 }
   5164 
   5165                 e.setFilters(nf);
   5166                 return;
   5167             }
   5168         }
   5169         e.setFilters(filters);
   5170     }
   5171 
   5172     /**
   5173      * Returns the current list of input filters.
   5174      *
   5175      * @attr ref android.R.styleable#TextView_maxLength
   5176      */
   5177     public InputFilter[] getFilters() {
   5178         return mFilters;
   5179     }
   5180 
   5181     /////////////////////////////////////////////////////////////////////////
   5182 
   5183     private int getBoxHeight(Layout l) {
   5184         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
   5185         int padding = (l == mHintLayout) ?
   5186                 getCompoundPaddingTop() + getCompoundPaddingBottom() :
   5187                 getExtendedPaddingTop() + getExtendedPaddingBottom();
   5188         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
   5189     }
   5190 
   5191     int getVerticalOffset(boolean forceNormal) {
   5192         int voffset = 0;
   5193         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   5194 
   5195         Layout l = mLayout;
   5196         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   5197             l = mHintLayout;
   5198         }
   5199 
   5200         if (gravity != Gravity.TOP) {
   5201             int boxht = getBoxHeight(l);
   5202             int textht = l.getHeight();
   5203 
   5204             if (textht < boxht) {
   5205                 if (gravity == Gravity.BOTTOM)
   5206                     voffset = boxht - textht;
   5207                 else // (gravity == Gravity.CENTER_VERTICAL)
   5208                     voffset = (boxht - textht) >> 1;
   5209             }
   5210         }
   5211         return voffset;
   5212     }
   5213 
   5214     private int getBottomVerticalOffset(boolean forceNormal) {
   5215         int voffset = 0;
   5216         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   5217 
   5218         Layout l = mLayout;
   5219         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   5220             l = mHintLayout;
   5221         }
   5222 
   5223         if (gravity != Gravity.BOTTOM) {
   5224             int boxht = getBoxHeight(l);
   5225             int textht = l.getHeight();
   5226 
   5227             if (textht < boxht) {
   5228                 if (gravity == Gravity.TOP)
   5229                     voffset = boxht - textht;
   5230                 else // (gravity == Gravity.CENTER_VERTICAL)
   5231                     voffset = (boxht - textht) >> 1;
   5232             }
   5233         }
   5234         return voffset;
   5235     }
   5236 
   5237     void invalidateCursorPath() {
   5238         if (mHighlightPathBogus) {
   5239             invalidateCursor();
   5240         } else {
   5241             final int horizontalPadding = getCompoundPaddingLeft();
   5242             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
   5243 
   5244             if (mEditor.mCursorCount == 0) {
   5245                 synchronized (TEMP_RECTF) {
   5246                     /*
   5247                      * The reason for this concern about the thickness of the
   5248                      * cursor and doing the floor/ceil on the coordinates is that
   5249                      * some EditTexts (notably textfields in the Browser) have
   5250                      * anti-aliased text where not all the characters are
   5251                      * necessarily at integer-multiple locations.  This should
   5252                      * make sure the entire cursor gets invalidated instead of
   5253                      * sometimes missing half a pixel.
   5254                      */
   5255                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
   5256                     if (thick < 1.0f) {
   5257                         thick = 1.0f;
   5258                     }
   5259 
   5260                     thick /= 2.0f;
   5261 
   5262                     // mHighlightPath is guaranteed to be non null at that point.
   5263                     mHighlightPath.computeBounds(TEMP_RECTF, false);
   5264 
   5265                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
   5266                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
   5267                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
   5268                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
   5269                 }
   5270             } else {
   5271                 for (int i = 0; i < mEditor.mCursorCount; i++) {
   5272                     Rect bounds = mEditor.mCursorDrawable[i].getBounds();
   5273                     invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
   5274                             bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
   5275                 }
   5276             }
   5277         }
   5278     }
   5279 
   5280     void invalidateCursor() {
   5281         int where = getSelectionEnd();
   5282 
   5283         invalidateCursor(where, where, where);
   5284     }
   5285 
   5286     private void invalidateCursor(int a, int b, int c) {
   5287         if (a >= 0 || b >= 0 || c >= 0) {
   5288             int start = Math.min(Math.min(a, b), c);
   5289             int end = Math.max(Math.max(a, b), c);
   5290             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
   5291         }
   5292     }
   5293 
   5294     /**
   5295      * Invalidates the region of text enclosed between the start and end text offsets.
   5296      */
   5297     void invalidateRegion(int start, int end, boolean invalidateCursor) {
   5298         if (mLayout == null) {
   5299             invalidate();
   5300         } else {
   5301                 int lineStart = mLayout.getLineForOffset(start);
   5302                 int top = mLayout.getLineTop(lineStart);
   5303 
   5304                 // This is ridiculous, but the descent from the line above
   5305                 // can hang down into the line we really want to redraw,
   5306                 // so we have to invalidate part of the line above to make
   5307                 // sure everything that needs to be redrawn really is.
   5308                 // (But not the whole line above, because that would cause
   5309                 // the same problem with the descenders on the line above it!)
   5310                 if (lineStart > 0) {
   5311                     top -= mLayout.getLineDescent(lineStart - 1);
   5312                 }
   5313 
   5314                 int lineEnd;
   5315 
   5316                 if (start == end)
   5317                     lineEnd = lineStart;
   5318                 else
   5319                     lineEnd = mLayout.getLineForOffset(end);
   5320 
   5321                 int bottom = mLayout.getLineBottom(lineEnd);
   5322 
   5323                 // mEditor can be null in case selection is set programmatically.
   5324                 if (invalidateCursor && mEditor != null) {
   5325                     for (int i = 0; i < mEditor.mCursorCount; i++) {
   5326                         Rect bounds = mEditor.mCursorDrawable[i].getBounds();
   5327                         top = Math.min(top, bounds.top);
   5328                         bottom = Math.max(bottom, bounds.bottom);
   5329                     }
   5330                 }
   5331 
   5332                 final int compoundPaddingLeft = getCompoundPaddingLeft();
   5333                 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
   5334 
   5335                 int left, right;
   5336                 if (lineStart == lineEnd && !invalidateCursor) {
   5337                     left = (int) mLayout.getPrimaryHorizontal(start);
   5338                     right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
   5339                     left += compoundPaddingLeft;
   5340                     right += compoundPaddingLeft;
   5341                 } else {
   5342                     // Rectangle bounding box when the region spans several lines
   5343                     left = compoundPaddingLeft;
   5344                     right = getWidth() - getCompoundPaddingRight();
   5345                 }
   5346 
   5347                 invalidate(mScrollX + left, verticalPadding + top,
   5348                         mScrollX + right, verticalPadding + bottom);
   5349         }
   5350     }
   5351 
   5352     private void registerForPreDraw() {
   5353         if (!mPreDrawRegistered) {
   5354             getViewTreeObserver().addOnPreDrawListener(this);
   5355             mPreDrawRegistered = true;
   5356         }
   5357     }
   5358 
   5359     private void unregisterForPreDraw() {
   5360         getViewTreeObserver().removeOnPreDrawListener(this);
   5361         mPreDrawRegistered = false;
   5362         mPreDrawListenerDetached = false;
   5363     }
   5364 
   5365     /**
   5366      * {@inheritDoc}
   5367      */
   5368     public boolean onPreDraw() {
   5369         if (mLayout == null) {
   5370             assumeLayout();
   5371         }
   5372 
   5373         if (mMovement != null) {
   5374             /* This code also provides auto-scrolling when a cursor is moved using a
   5375              * CursorController (insertion point or selection limits).
   5376              * For selection, ensure start or end is visible depending on controller's state.
   5377              */
   5378             int curs = getSelectionEnd();
   5379             // Do not create the controller if it is not already created.
   5380             if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
   5381                     mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
   5382                 curs = getSelectionStart();
   5383             }
   5384 
   5385             /*
   5386              * TODO: This should really only keep the end in view if
   5387              * it already was before the text changed.  I'm not sure
   5388              * of a good way to tell from here if it was.
   5389              */
   5390             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   5391                 curs = mText.length();
   5392             }
   5393 
   5394             if (curs >= 0) {
   5395                 bringPointIntoView(curs);
   5396             }
   5397         } else {
   5398             bringTextIntoView();
   5399         }
   5400 
   5401         // This has to be checked here since:
   5402         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
   5403         //   a screen rotation) since layout is not yet initialized at that point.
   5404         if (mEditor != null && mEditor.mCreatedWithASelection) {
   5405             mEditor.refreshTextActionMode();
   5406             mEditor.mCreatedWithASelection = false;
   5407         }
   5408 
   5409         unregisterForPreDraw();
   5410 
   5411         return true;
   5412     }
   5413 
   5414     @Override
   5415     protected void onAttachedToWindow() {
   5416         super.onAttachedToWindow();
   5417 
   5418         if (mEditor != null) mEditor.onAttachedToWindow();
   5419 
   5420         if (mPreDrawListenerDetached) {
   5421             getViewTreeObserver().addOnPreDrawListener(this);
   5422             mPreDrawListenerDetached = false;
   5423         }
   5424     }
   5425 
   5426     /** @hide */
   5427     @Override
   5428     protected void onDetachedFromWindowInternal() {
   5429         if (mPreDrawRegistered) {
   5430             getViewTreeObserver().removeOnPreDrawListener(this);
   5431             mPreDrawListenerDetached = true;
   5432         }
   5433 
   5434         resetResolvedDrawables();
   5435 
   5436         if (mEditor != null) mEditor.onDetachedFromWindow();
   5437 
   5438         super.onDetachedFromWindowInternal();
   5439     }
   5440 
   5441     @Override
   5442     public void onScreenStateChanged(int screenState) {
   5443         super.onScreenStateChanged(screenState);
   5444         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
   5445     }
   5446 
   5447     @Override
   5448     protected boolean isPaddingOffsetRequired() {
   5449         return mShadowRadius != 0 || mDrawables != null;
   5450     }
   5451 
   5452     @Override
   5453     protected int getLeftPaddingOffset() {
   5454         return getCompoundPaddingLeft() - mPaddingLeft +
   5455                 (int) Math.min(0, mShadowDx - mShadowRadius);
   5456     }
   5457 
   5458     @Override
   5459     protected int getTopPaddingOffset() {
   5460         return (int) Math.min(0, mShadowDy - mShadowRadius);
   5461     }
   5462 
   5463     @Override
   5464     protected int getBottomPaddingOffset() {
   5465         return (int) Math.max(0, mShadowDy + mShadowRadius);
   5466     }
   5467 
   5468     @Override
   5469     protected int getRightPaddingOffset() {
   5470         return -(getCompoundPaddingRight() - mPaddingRight) +
   5471                 (int) Math.max(0, mShadowDx + mShadowRadius);
   5472     }
   5473 
   5474     @Override
   5475     protected boolean verifyDrawable(@NonNull Drawable who) {
   5476         final boolean verified = super.verifyDrawable(who);
   5477         if (!verified && mDrawables != null) {
   5478             for (Drawable dr : mDrawables.mShowing) {
   5479                 if (who == dr) {
   5480                     return true;
   5481                 }
   5482             }
   5483         }
   5484         return verified;
   5485     }
   5486 
   5487     @Override
   5488     public void jumpDrawablesToCurrentState() {
   5489         super.jumpDrawablesToCurrentState();
   5490         if (mDrawables != null) {
   5491             for (Drawable dr : mDrawables.mShowing) {
   5492                 if (dr != null) {
   5493                     dr.jumpToCurrentState();
   5494                 }
   5495             }
   5496         }
   5497     }
   5498 
   5499     @Override
   5500     public void invalidateDrawable(@NonNull Drawable drawable) {
   5501         boolean handled = false;
   5502 
   5503         if (verifyDrawable(drawable)) {
   5504             final Rect dirty = drawable.getBounds();
   5505             int scrollX = mScrollX;
   5506             int scrollY = mScrollY;
   5507 
   5508             // IMPORTANT: The coordinates below are based on the coordinates computed
   5509             // for each compound drawable in onDraw(). Make sure to update each section
   5510             // accordingly.
   5511             final TextView.Drawables drawables = mDrawables;
   5512             if (drawables != null) {
   5513                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
   5514                     final int compoundPaddingTop = getCompoundPaddingTop();
   5515                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   5516                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   5517 
   5518                     scrollX += mPaddingLeft;
   5519                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
   5520                     handled = true;
   5521                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
   5522                     final int compoundPaddingTop = getCompoundPaddingTop();
   5523                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   5524                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   5525 
   5526                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
   5527                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
   5528                     handled = true;
   5529                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
   5530                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   5531                     final int compoundPaddingRight = getCompoundPaddingRight();
   5532                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   5533 
   5534                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
   5535                     scrollY += mPaddingTop;
   5536                     handled = true;
   5537                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
   5538                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   5539                     final int compoundPaddingRight = getCompoundPaddingRight();
   5540                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   5541 
   5542                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
   5543                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
   5544                     handled = true;
   5545                 }
   5546             }
   5547 
   5548             if (handled) {
   5549                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
   5550                         dirty.right + scrollX, dirty.bottom + scrollY);
   5551             }
   5552         }
   5553 
   5554         if (!handled) {
   5555             super.invalidateDrawable(drawable);
   5556         }
   5557     }
   5558 
   5559     @Override
   5560     public boolean hasOverlappingRendering() {
   5561         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
   5562         return ((getBackground() != null && getBackground().getCurrent() != null)
   5563                 || mText instanceof Spannable || hasSelection()
   5564                 || isHorizontalFadingEdgeEnabled());
   5565     }
   5566 
   5567     /**
   5568      *
   5569      * Returns the state of the {@code textIsSelectable} flag (See
   5570      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
   5571      * to allow users to select and copy text in a non-editable TextView, the content of an
   5572      * {@link EditText} can always be selected, independently of the value of this flag.
   5573      * <p>
   5574      *
   5575      * @return True if the text displayed in this TextView can be selected by the user.
   5576      *
   5577      * @attr ref android.R.styleable#TextView_textIsSelectable
   5578      */
   5579     public boolean isTextSelectable() {
   5580         return mEditor == null ? false : mEditor.mTextIsSelectable;
   5581     }
   5582 
   5583     /**
   5584      * Sets whether the content of this view is selectable by the user. The default is
   5585      * {@code false}, meaning that the content is not selectable.
   5586      * <p>
   5587      * When you use a TextView to display a useful piece of information to the user (such as a
   5588      * contact's address), make it selectable, so that the user can select and copy its
   5589      * content. You can also use set the XML attribute
   5590      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
   5591      * <p>
   5592      * When you call this method to set the value of {@code textIsSelectable}, it sets
   5593      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
   5594      * and {@code longClickable} to the same value. These flags correspond to the attributes
   5595      * {@link android.R.styleable#View_focusable android:focusable},
   5596      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
   5597      * {@link android.R.styleable#View_clickable android:clickable}, and
   5598      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
   5599      * flags to a state you had set previously, call one or more of the following methods:
   5600      * {@link #setFocusable(boolean) setFocusable()},
   5601      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
   5602      * {@link #setClickable(boolean) setClickable()} or
   5603      * {@link #setLongClickable(boolean) setLongClickable()}.
   5604      *
   5605      * @param selectable Whether the content of this TextView should be selectable.
   5606      */
   5607     public void setTextIsSelectable(boolean selectable) {
   5608         if (!selectable && mEditor == null) return; // false is default value with no edit data
   5609 
   5610         createEditorIfNeeded();
   5611         if (mEditor.mTextIsSelectable == selectable) return;
   5612 
   5613         mEditor.mTextIsSelectable = selectable;
   5614         setFocusableInTouchMode(selectable);
   5615         setFocusable(selectable);
   5616         setClickable(selectable);
   5617         setLongClickable(selectable);
   5618 
   5619         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
   5620 
   5621         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
   5622         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
   5623 
   5624         // Called by setText above, but safer in case of future code changes
   5625         mEditor.prepareCursorControllers();
   5626     }
   5627 
   5628     @Override
   5629     protected int[] onCreateDrawableState(int extraSpace) {
   5630         final int[] drawableState;
   5631 
   5632         if (mSingleLine) {
   5633             drawableState = super.onCreateDrawableState(extraSpace);
   5634         } else {
   5635             drawableState = super.onCreateDrawableState(extraSpace + 1);
   5636             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
   5637         }
   5638 
   5639         if (isTextSelectable()) {
   5640             // Disable pressed state, which was introduced when TextView was made clickable.
   5641             // Prevents text color change.
   5642             // setClickable(false) would have a similar effect, but it also disables focus changes
   5643             // and long press actions, which are both needed by text selection.
   5644             final int length = drawableState.length;
   5645             for (int i = 0; i < length; i++) {
   5646                 if (drawableState[i] == R.attr.state_pressed) {
   5647                     final int[] nonPressedState = new int[length - 1];
   5648                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
   5649                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
   5650                     return nonPressedState;
   5651                 }
   5652             }
   5653         }
   5654 
   5655         return drawableState;
   5656     }
   5657 
   5658     private Path getUpdatedHighlightPath() {
   5659         Path highlight = null;
   5660         Paint highlightPaint = mHighlightPaint;
   5661 
   5662         final int selStart = getSelectionStart();
   5663         final int selEnd = getSelectionEnd();
   5664         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
   5665             if (selStart == selEnd) {
   5666                 if (mEditor != null && mEditor.isCursorVisible() &&
   5667                         (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
   5668                         (2 * Editor.BLINK) < Editor.BLINK) {
   5669                     if (mHighlightPathBogus) {
   5670                         if (mHighlightPath == null) mHighlightPath = new Path();
   5671                         mHighlightPath.reset();
   5672                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
   5673                         mEditor.updateCursorsPositions();
   5674                         mHighlightPathBogus = false;
   5675                     }
   5676 
   5677                     // XXX should pass to skin instead of drawing directly
   5678                     highlightPaint.setColor(mCurTextColor);
   5679                     highlightPaint.setStyle(Paint.Style.STROKE);
   5680                     highlight = mHighlightPath;
   5681                 }
   5682             } else {
   5683                 if (mHighlightPathBogus) {
   5684                     if (mHighlightPath == null) mHighlightPath = new Path();
   5685                     mHighlightPath.reset();
   5686                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   5687                     mHighlightPathBogus = false;
   5688                 }
   5689 
   5690                 // XXX should pass to skin instead of drawing directly
   5691                 highlightPaint.setColor(mHighlightColor);
   5692                 highlightPaint.setStyle(Paint.Style.FILL);
   5693 
   5694                 highlight = mHighlightPath;
   5695             }
   5696         }
   5697         return highlight;
   5698     }
   5699 
   5700     /**
   5701      * @hide
   5702      */
   5703     public int getHorizontalOffsetForDrawables() {
   5704         return 0;
   5705     }
   5706 
   5707     @Override
   5708     protected void onDraw(Canvas canvas) {
   5709         restartMarqueeIfNeeded();
   5710 
   5711         // Draw the background for this view
   5712         super.onDraw(canvas);
   5713 
   5714         final int compoundPaddingLeft = getCompoundPaddingLeft();
   5715         final int compoundPaddingTop = getCompoundPaddingTop();
   5716         final int compoundPaddingRight = getCompoundPaddingRight();
   5717         final int compoundPaddingBottom = getCompoundPaddingBottom();
   5718         final int scrollX = mScrollX;
   5719         final int scrollY = mScrollY;
   5720         final int right = mRight;
   5721         final int left = mLeft;
   5722         final int bottom = mBottom;
   5723         final int top = mTop;
   5724         final boolean isLayoutRtl = isLayoutRtl();
   5725         final int offset = getHorizontalOffsetForDrawables();
   5726         final int leftOffset = isLayoutRtl ? 0 : offset;
   5727         final int rightOffset = isLayoutRtl ? offset : 0 ;
   5728 
   5729         final Drawables dr = mDrawables;
   5730         if (dr != null) {
   5731             /*
   5732              * Compound, not extended, because the icon is not clipped
   5733              * if the text height is smaller.
   5734              */
   5735 
   5736             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
   5737             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
   5738 
   5739             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   5740             // Make sure to update invalidateDrawable() when changing this code.
   5741             if (dr.mShowing[Drawables.LEFT] != null) {
   5742                 canvas.save();
   5743                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
   5744                                  scrollY + compoundPaddingTop +
   5745                                  (vspace - dr.mDrawableHeightLeft) / 2);
   5746                 dr.mShowing[Drawables.LEFT].draw(canvas);
   5747                 canvas.restore();
   5748             }
   5749 
   5750             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   5751             // Make sure to update invalidateDrawable() when changing this code.
   5752             if (dr.mShowing[Drawables.RIGHT] != null) {
   5753                 canvas.save();
   5754                 canvas.translate(scrollX + right - left - mPaddingRight
   5755                         - dr.mDrawableSizeRight - rightOffset,
   5756                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
   5757                 dr.mShowing[Drawables.RIGHT].draw(canvas);
   5758                 canvas.restore();
   5759             }
   5760 
   5761             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   5762             // Make sure to update invalidateDrawable() when changing this code.
   5763             if (dr.mShowing[Drawables.TOP] != null) {
   5764                 canvas.save();
   5765                 canvas.translate(scrollX + compoundPaddingLeft +
   5766                         (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
   5767                 dr.mShowing[Drawables.TOP].draw(canvas);
   5768                 canvas.restore();
   5769             }
   5770 
   5771             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   5772             // Make sure to update invalidateDrawable() when changing this code.
   5773             if (dr.mShowing[Drawables.BOTTOM] != null) {
   5774                 canvas.save();
   5775                 canvas.translate(scrollX + compoundPaddingLeft +
   5776                         (hspace - dr.mDrawableWidthBottom) / 2,
   5777                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
   5778                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
   5779                 canvas.restore();
   5780             }
   5781         }
   5782 
   5783         int color = mCurTextColor;
   5784 
   5785         if (mLayout == null) {
   5786             assumeLayout();
   5787         }
   5788 
   5789         Layout layout = mLayout;
   5790 
   5791         if (mHint != null && mText.length() == 0) {
   5792             if (mHintTextColor != null) {
   5793                 color = mCurHintTextColor;
   5794             }
   5795 
   5796             layout = mHintLayout;
   5797         }
   5798 
   5799         mTextPaint.setColor(color);
   5800         mTextPaint.drawableState = getDrawableState();
   5801 
   5802         canvas.save();
   5803         /*  Would be faster if we didn't have to do this. Can we chop the
   5804             (displayable) text so that we don't need to do this ever?
   5805         */
   5806 
   5807         int extendedPaddingTop = getExtendedPaddingTop();
   5808         int extendedPaddingBottom = getExtendedPaddingBottom();
   5809 
   5810         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   5811         final int maxScrollY = mLayout.getHeight() - vspace;
   5812 
   5813         float clipLeft = compoundPaddingLeft + scrollX;
   5814         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
   5815         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
   5816         float clipBottom = bottom - top + scrollY -
   5817                 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
   5818 
   5819         if (mShadowRadius != 0) {
   5820             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
   5821             clipRight += Math.max(0, mShadowDx + mShadowRadius);
   5822 
   5823             clipTop += Math.min(0, mShadowDy - mShadowRadius);
   5824             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
   5825         }
   5826 
   5827         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
   5828 
   5829         int voffsetText = 0;
   5830         int voffsetCursor = 0;
   5831 
   5832         // translate in by our padding
   5833         /* shortcircuit calling getVerticaOffset() */
   5834         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   5835             voffsetText = getVerticalOffset(false);
   5836             voffsetCursor = getVerticalOffset(true);
   5837         }
   5838         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
   5839 
   5840         final int layoutDirection = getLayoutDirection();
   5841         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
   5842         if (isMarqueeFadeEnabled()) {
   5843             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
   5844                     (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
   5845                 final int width = mRight - mLeft;
   5846                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
   5847                 final float dx = mLayout.getLineRight(0) - (width - padding);
   5848                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   5849             }
   5850 
   5851             if (mMarquee != null && mMarquee.isRunning()) {
   5852                 final float dx = -mMarquee.getScroll();
   5853                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   5854             }
   5855         }
   5856 
   5857         final int cursorOffsetVertical = voffsetCursor - voffsetText;
   5858 
   5859         Path highlight = getUpdatedHighlightPath();
   5860         if (mEditor != null) {
   5861             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
   5862         } else {
   5863             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
   5864         }
   5865 
   5866         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
   5867             final float dx = mMarquee.getGhostOffset();
   5868             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   5869             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
   5870         }
   5871 
   5872         canvas.restore();
   5873     }
   5874 
   5875     @Override
   5876     public void getFocusedRect(Rect r) {
   5877         if (mLayout == null) {
   5878             super.getFocusedRect(r);
   5879             return;
   5880         }
   5881 
   5882         int selEnd = getSelectionEnd();
   5883         if (selEnd < 0) {
   5884             super.getFocusedRect(r);
   5885             return;
   5886         }
   5887 
   5888         int selStart = getSelectionStart();
   5889         if (selStart < 0 || selStart >= selEnd) {
   5890             int line = mLayout.getLineForOffset(selEnd);
   5891             r.top = mLayout.getLineTop(line);
   5892             r.bottom = mLayout.getLineBottom(line);
   5893             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
   5894             r.right = r.left + 4;
   5895         } else {
   5896             int lineStart = mLayout.getLineForOffset(selStart);
   5897             int lineEnd = mLayout.getLineForOffset(selEnd);
   5898             r.top = mLayout.getLineTop(lineStart);
   5899             r.bottom = mLayout.getLineBottom(lineEnd);
   5900             if (lineStart == lineEnd) {
   5901                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
   5902                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
   5903             } else {
   5904                 // Selection extends across multiple lines -- make the focused
   5905                 // rect cover the entire width.
   5906                 if (mHighlightPathBogus) {
   5907                     if (mHighlightPath == null) mHighlightPath = new Path();
   5908                     mHighlightPath.reset();
   5909                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   5910                     mHighlightPathBogus = false;
   5911                 }
   5912                 synchronized (TEMP_RECTF) {
   5913                     mHighlightPath.computeBounds(TEMP_RECTF, true);
   5914                     r.left = (int)TEMP_RECTF.left-1;
   5915                     r.right = (int)TEMP_RECTF.right+1;
   5916                 }
   5917             }
   5918         }
   5919 
   5920         // Adjust for padding and gravity.
   5921         int paddingLeft = getCompoundPaddingLeft();
   5922         int paddingTop = getExtendedPaddingTop();
   5923         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   5924             paddingTop += getVerticalOffset(false);
   5925         }
   5926         r.offset(paddingLeft, paddingTop);
   5927         int paddingBottom = getExtendedPaddingBottom();
   5928         r.bottom += paddingBottom;
   5929     }
   5930 
   5931     /**
   5932      * Return the number of lines of text, or 0 if the internal Layout has not
   5933      * been built.
   5934      */
   5935     public int getLineCount() {
   5936         return mLayout != null ? mLayout.getLineCount() : 0;
   5937     }
   5938 
   5939     /**
   5940      * Return the baseline for the specified line (0...getLineCount() - 1)
   5941      * If bounds is not null, return the top, left, right, bottom extents
   5942      * of the specified line in it. If the internal Layout has not been built,
   5943      * return 0 and set bounds to (0, 0, 0, 0)
   5944      * @param line which line to examine (0..getLineCount() - 1)
   5945      * @param bounds Optional. If not null, it returns the extent of the line
   5946      * @return the Y-coordinate of the baseline
   5947      */
   5948     public int getLineBounds(int line, Rect bounds) {
   5949         if (mLayout == null) {
   5950             if (bounds != null) {
   5951                 bounds.set(0, 0, 0, 0);
   5952             }
   5953             return 0;
   5954         }
   5955         else {
   5956             int baseline = mLayout.getLineBounds(line, bounds);
   5957 
   5958             int voffset = getExtendedPaddingTop();
   5959             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   5960                 voffset += getVerticalOffset(true);
   5961             }
   5962             if (bounds != null) {
   5963                 bounds.offset(getCompoundPaddingLeft(), voffset);
   5964             }
   5965             return baseline + voffset;
   5966         }
   5967     }
   5968 
   5969     @Override
   5970     public int getBaseline() {
   5971         if (mLayout == null) {
   5972             return super.getBaseline();
   5973         }
   5974 
   5975         return getBaselineOffset() + mLayout.getLineBaseline(0);
   5976     }
   5977 
   5978     int getBaselineOffset() {
   5979         int voffset = 0;
   5980         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   5981             voffset = getVerticalOffset(true);
   5982         }
   5983 
   5984         if (isLayoutModeOptical(mParent)) {
   5985             voffset -= getOpticalInsets().top;
   5986         }
   5987 
   5988         return getExtendedPaddingTop() + voffset;
   5989     }
   5990 
   5991     /**
   5992      * @hide
   5993      */
   5994     @Override
   5995     protected int getFadeTop(boolean offsetRequired) {
   5996         if (mLayout == null) return 0;
   5997 
   5998         int voffset = 0;
   5999         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   6000             voffset = getVerticalOffset(true);
   6001         }
   6002 
   6003         if (offsetRequired) voffset += getTopPaddingOffset();
   6004 
   6005         return getExtendedPaddingTop() + voffset;
   6006     }
   6007 
   6008     /**
   6009      * @hide
   6010      */
   6011     @Override
   6012     protected int getFadeHeight(boolean offsetRequired) {
   6013         return mLayout != null ? mLayout.getHeight() : 0;
   6014     }
   6015 
   6016     @Override
   6017     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
   6018         if (mText instanceof Spannable && mLinksClickable) {
   6019             final float x = event.getX(pointerIndex);
   6020             final float y = event.getY(pointerIndex);
   6021             final int offset = getOffsetForPosition(x, y);
   6022             final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
   6023                     ClickableSpan.class);
   6024             if (clickables.length > 0) {
   6025                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
   6026             }
   6027         }
   6028         if (isTextSelectable() || isTextEditable()) {
   6029             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
   6030         }
   6031         return super.onResolvePointerIcon(event, pointerIndex);
   6032     }
   6033 
   6034     @Override
   6035     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
   6036         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
   6037         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
   6038         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
   6039         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
   6040             return true;
   6041         }
   6042         return super.onKeyPreIme(keyCode, event);
   6043     }
   6044 
   6045     /**
   6046      * @hide
   6047      */
   6048     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
   6049         // Do nothing unless mEditor is in text action mode.
   6050         if (mEditor == null || mEditor.mTextActionMode == null) {
   6051             return false;
   6052         }
   6053 
   6054         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
   6055             KeyEvent.DispatcherState state = getKeyDispatcherState();
   6056             if (state != null) {
   6057                 state.startTracking(event, this);
   6058             }
   6059             return true;
   6060         } else if (event.getAction() == KeyEvent.ACTION_UP) {
   6061             KeyEvent.DispatcherState state = getKeyDispatcherState();
   6062             if (state != null) {
   6063                 state.handleUpEvent(event);
   6064             }
   6065             if (event.isTracking() && !event.isCanceled()) {
   6066                 stopTextActionMode();
   6067                 return true;
   6068             }
   6069         }
   6070         return false;
   6071     }
   6072 
   6073     @Override
   6074     public boolean onKeyDown(int keyCode, KeyEvent event) {
   6075         final int which = doKeyDown(keyCode, event, null);
   6076         if (which == KEY_EVENT_NOT_HANDLED) {
   6077             return super.onKeyDown(keyCode, event);
   6078         }
   6079 
   6080         return true;
   6081     }
   6082 
   6083     @Override
   6084     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   6085         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
   6086         final int which = doKeyDown(keyCode, down, event);
   6087         if (which == KEY_EVENT_NOT_HANDLED) {
   6088             // Go through default dispatching.
   6089             return super.onKeyMultiple(keyCode, repeatCount, event);
   6090         }
   6091         if (which == KEY_EVENT_HANDLED) {
   6092             // Consumed the whole thing.
   6093             return true;
   6094         }
   6095 
   6096         repeatCount--;
   6097 
   6098         // We are going to dispatch the remaining events to either the input
   6099         // or movement method.  To do this, we will just send a repeated stream
   6100         // of down and up events until we have done the complete repeatCount.
   6101         // It would be nice if those interfaces had an onKeyMultiple() method,
   6102         // but adding that is a more complicated change.
   6103         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
   6104         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
   6105             // mEditor and mEditor.mInput are not null from doKeyDown
   6106             mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
   6107             while (--repeatCount > 0) {
   6108                 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
   6109                 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
   6110             }
   6111             hideErrorIfUnchanged();
   6112 
   6113         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
   6114             // mMovement is not null from doKeyDown
   6115             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   6116             while (--repeatCount > 0) {
   6117                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
   6118                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
   6119             }
   6120         }
   6121 
   6122         return true;
   6123     }
   6124 
   6125     /**
   6126      * Returns true if pressing ENTER in this field advances focus instead
   6127      * of inserting the character.  This is true mostly in single-line fields,
   6128      * but also in mail addresses and subjects which will display on multiple
   6129      * lines but where it doesn't make sense to insert newlines.
   6130      */
   6131     private boolean shouldAdvanceFocusOnEnter() {
   6132         if (getKeyListener() == null) {
   6133             return false;
   6134         }
   6135 
   6136         if (mSingleLine) {
   6137             return true;
   6138         }
   6139 
   6140         if (mEditor != null &&
   6141                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
   6142             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   6143             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
   6144                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
   6145                 return true;
   6146             }
   6147         }
   6148 
   6149         return false;
   6150     }
   6151 
   6152     /**
   6153      * Returns true if pressing TAB in this field advances focus instead
   6154      * of inserting the character.  Insert tabs only in multi-line editors.
   6155      */
   6156     private boolean shouldAdvanceFocusOnTab() {
   6157         if (getKeyListener() != null && !mSingleLine && mEditor != null &&
   6158                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
   6159             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   6160             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
   6161                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
   6162                 return false;
   6163             }
   6164         }
   6165         return true;
   6166     }
   6167 
   6168     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
   6169         if (!isEnabled()) {
   6170             return KEY_EVENT_NOT_HANDLED;
   6171         }
   6172 
   6173         // If this is the initial keydown, we don't want to prevent a movement away from this view.
   6174         // While this shouldn't be necessary because any time we're preventing default movement we
   6175         // should be restricting the focus to remain within this view, thus we'll also receive
   6176         // the key up event, occasionally key up events will get dropped and we don't want to
   6177         // prevent the user from traversing out of this on the next key down.
   6178         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
   6179             mPreventDefaultMovement = false;
   6180         }
   6181 
   6182         switch (keyCode) {
   6183             case KeyEvent.KEYCODE_ENTER:
   6184                 if (event.hasNoModifiers()) {
   6185                     // When mInputContentType is set, we know that we are
   6186                     // running in a "modern" cupcake environment, so don't need
   6187                     // to worry about the application trying to capture
   6188                     // enter key events.
   6189                     if (mEditor != null && mEditor.mInputContentType != null) {
   6190                         // If there is an action listener, given them a
   6191                         // chance to consume the event.
   6192                         if (mEditor.mInputContentType.onEditorActionListener != null &&
   6193                                 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
   6194                                 this, EditorInfo.IME_NULL, event)) {
   6195                             mEditor.mInputContentType.enterDown = true;
   6196                             // We are consuming the enter key for them.
   6197                             return KEY_EVENT_HANDLED;
   6198                         }
   6199                     }
   6200 
   6201                     // If our editor should move focus when enter is pressed, or
   6202                     // this is a generated event from an IME action button, then
   6203                     // don't let it be inserted into the text.
   6204                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
   6205                             || shouldAdvanceFocusOnEnter()) {
   6206                         if (hasOnClickListeners()) {
   6207                             return KEY_EVENT_NOT_HANDLED;
   6208                         }
   6209                         return KEY_EVENT_HANDLED;
   6210                     }
   6211                 }
   6212                 break;
   6213 
   6214             case KeyEvent.KEYCODE_DPAD_CENTER:
   6215                 if (event.hasNoModifiers()) {
   6216                     if (shouldAdvanceFocusOnEnter()) {
   6217                         return KEY_EVENT_NOT_HANDLED;
   6218                     }
   6219                 }
   6220                 break;
   6221 
   6222             case KeyEvent.KEYCODE_TAB:
   6223                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   6224                     if (shouldAdvanceFocusOnTab()) {
   6225                         return KEY_EVENT_NOT_HANDLED;
   6226                     }
   6227                 }
   6228                 break;
   6229 
   6230                 // Has to be done on key down (and not on key up) to correctly be intercepted.
   6231             case KeyEvent.KEYCODE_BACK:
   6232                 if (mEditor != null && mEditor.mTextActionMode != null) {
   6233                     stopTextActionMode();
   6234                     return KEY_EVENT_HANDLED;
   6235                 }
   6236                 break;
   6237 
   6238             case KeyEvent.KEYCODE_CUT:
   6239                 if (event.hasNoModifiers() && canCut()) {
   6240                     if (onTextContextMenuItem(ID_CUT)) {
   6241                         return KEY_EVENT_HANDLED;
   6242                     }
   6243                 }
   6244                 break;
   6245 
   6246             case KeyEvent.KEYCODE_COPY:
   6247                 if (event.hasNoModifiers() && canCopy()) {
   6248                     if (onTextContextMenuItem(ID_COPY)) {
   6249                         return KEY_EVENT_HANDLED;
   6250                     }
   6251                 }
   6252                 break;
   6253 
   6254             case KeyEvent.KEYCODE_PASTE:
   6255                 if (event.hasNoModifiers() && canPaste()) {
   6256                     if (onTextContextMenuItem(ID_PASTE)) {
   6257                         return KEY_EVENT_HANDLED;
   6258                     }
   6259                 }
   6260                 break;
   6261         }
   6262 
   6263         if (mEditor != null && mEditor.mKeyListener != null) {
   6264             boolean doDown = true;
   6265             if (otherEvent != null) {
   6266                 try {
   6267                     beginBatchEdit();
   6268                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
   6269                             otherEvent);
   6270                     hideErrorIfUnchanged();
   6271                     doDown = false;
   6272                     if (handled) {
   6273                         return KEY_EVENT_HANDLED;
   6274                     }
   6275                 } catch (AbstractMethodError e) {
   6276                     // onKeyOther was added after 1.0, so if it isn't
   6277                     // implemented we need to try to dispatch as a regular down.
   6278                 } finally {
   6279                     endBatchEdit();
   6280                 }
   6281             }
   6282 
   6283             if (doDown) {
   6284                 beginBatchEdit();
   6285                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
   6286                         keyCode, event);
   6287                 endBatchEdit();
   6288                 hideErrorIfUnchanged();
   6289                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
   6290             }
   6291         }
   6292 
   6293         // bug 650865: sometimes we get a key event before a layout.
   6294         // don't try to move around if we don't know the layout.
   6295 
   6296         if (mMovement != null && mLayout != null) {
   6297             boolean doDown = true;
   6298             if (otherEvent != null) {
   6299                 try {
   6300                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
   6301                             otherEvent);
   6302                     doDown = false;
   6303                     if (handled) {
   6304                         return KEY_EVENT_HANDLED;
   6305                     }
   6306                 } catch (AbstractMethodError e) {
   6307                     // onKeyOther was added after 1.0, so if it isn't
   6308                     // implemented we need to try to dispatch as a regular down.
   6309                 }
   6310             }
   6311             if (doDown) {
   6312                 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
   6313                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
   6314                         mPreventDefaultMovement = true;
   6315                     }
   6316                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
   6317                 }
   6318             }
   6319         }
   6320 
   6321         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
   6322                 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
   6323     }
   6324 
   6325     /**
   6326      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
   6327      * can be recorded.
   6328      * @hide
   6329      */
   6330     public void resetErrorChangedFlag() {
   6331         /*
   6332          * Keep track of what the error was before doing the input
   6333          * so that if an input filter changed the error, we leave
   6334          * that error showing.  Otherwise, we take down whatever
   6335          * error was showing when the user types something.
   6336          */
   6337         if (mEditor != null) mEditor.mErrorWasChanged = false;
   6338     }
   6339 
   6340     /**
   6341      * @hide
   6342      */
   6343     public void hideErrorIfUnchanged() {
   6344         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
   6345             setError(null, null);
   6346         }
   6347     }
   6348 
   6349     @Override
   6350     public boolean onKeyUp(int keyCode, KeyEvent event) {
   6351         if (!isEnabled()) {
   6352             return super.onKeyUp(keyCode, event);
   6353         }
   6354 
   6355         if (!KeyEvent.isModifierKey(keyCode)) {
   6356             mPreventDefaultMovement = false;
   6357         }
   6358 
   6359         switch (keyCode) {
   6360             case KeyEvent.KEYCODE_DPAD_CENTER:
   6361                 if (event.hasNoModifiers()) {
   6362                     /*
   6363                      * If there is a click listener, just call through to
   6364                      * super, which will invoke it.
   6365                      *
   6366                      * If there isn't a click listener, try to show the soft
   6367                      * input method.  (It will also
   6368                      * call performClick(), but that won't do anything in
   6369                      * this case.)
   6370                      */
   6371                     if (!hasOnClickListeners()) {
   6372                         if (mMovement != null && mText instanceof Editable
   6373                                 && mLayout != null && onCheckIsTextEditor()) {
   6374                             InputMethodManager imm = InputMethodManager.peekInstance();
   6375                             viewClicked(imm);
   6376                             if (imm != null && getShowSoftInputOnFocus()) {
   6377                                 imm.showSoftInput(this, 0);
   6378                             }
   6379                         }
   6380                     }
   6381                 }
   6382                 return super.onKeyUp(keyCode, event);
   6383 
   6384             case KeyEvent.KEYCODE_ENTER:
   6385                 if (event.hasNoModifiers()) {
   6386                     if (mEditor != null && mEditor.mInputContentType != null
   6387                             && mEditor.mInputContentType.onEditorActionListener != null
   6388                             && mEditor.mInputContentType.enterDown) {
   6389                         mEditor.mInputContentType.enterDown = false;
   6390                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
   6391                                 this, EditorInfo.IME_NULL, event)) {
   6392                             return true;
   6393                         }
   6394                     }
   6395 
   6396                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
   6397                             || shouldAdvanceFocusOnEnter()) {
   6398                         /*
   6399                          * If there is a click listener, just call through to
   6400                          * super, which will invoke it.
   6401                          *
   6402                          * If there isn't a click listener, try to advance focus,
   6403                          * but still call through to super, which will reset the
   6404                          * pressed state and longpress state.  (It will also
   6405                          * call performClick(), but that won't do anything in
   6406                          * this case.)
   6407                          */
   6408                         if (!hasOnClickListeners()) {
   6409                             View v = focusSearch(FOCUS_DOWN);
   6410 
   6411                             if (v != null) {
   6412                                 if (!v.requestFocus(FOCUS_DOWN)) {
   6413                                     throw new IllegalStateException(
   6414                                             "focus search returned a view " +
   6415                                             "that wasn't able to take focus!");
   6416                                 }
   6417 
   6418                                 /*
   6419                                  * Return true because we handled the key; super
   6420                                  * will return false because there was no click
   6421                                  * listener.
   6422                                  */
   6423                                 super.onKeyUp(keyCode, event);
   6424                                 return true;
   6425                             } else if ((event.getFlags()
   6426                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
   6427                                 // No target for next focus, but make sure the IME
   6428                                 // if this came from it.
   6429                                 InputMethodManager imm = InputMethodManager.peekInstance();
   6430                                 if (imm != null && imm.isActive(this)) {
   6431                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   6432                                 }
   6433                             }
   6434                         }
   6435                     }
   6436                     return super.onKeyUp(keyCode, event);
   6437                 }
   6438                 break;
   6439         }
   6440 
   6441         if (mEditor != null && mEditor.mKeyListener != null)
   6442             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
   6443                 return true;
   6444 
   6445         if (mMovement != null && mLayout != null)
   6446             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
   6447                 return true;
   6448 
   6449         return super.onKeyUp(keyCode, event);
   6450     }
   6451 
   6452     @Override
   6453     public boolean onCheckIsTextEditor() {
   6454         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
   6455     }
   6456 
   6457     @Override
   6458     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   6459         if (onCheckIsTextEditor() && isEnabled()) {
   6460             mEditor.createInputMethodStateIfNeeded();
   6461             outAttrs.inputType = getInputType();
   6462             if (mEditor.mInputContentType != null) {
   6463                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
   6464                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
   6465                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
   6466                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
   6467                 outAttrs.extras = mEditor.mInputContentType.extras;
   6468                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
   6469             } else {
   6470                 outAttrs.imeOptions = EditorInfo.IME_NULL;
   6471                 outAttrs.hintLocales = null;
   6472             }
   6473             if (focusSearch(FOCUS_DOWN) != null) {
   6474                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
   6475             }
   6476             if (focusSearch(FOCUS_UP) != null) {
   6477                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
   6478             }
   6479             if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
   6480                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
   6481                 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
   6482                     // An action has not been set, but the enter key will move to
   6483                     // the next focus, so set the action to that.
   6484                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
   6485                 } else {
   6486                     // An action has not been set, and there is no focus to move
   6487                     // to, so let's just supply a "done" action.
   6488                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
   6489                 }
   6490                 if (!shouldAdvanceFocusOnEnter()) {
   6491                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   6492                 }
   6493             }
   6494             if (isMultilineInputType(outAttrs.inputType)) {
   6495                 // Multi-line text editors should always show an enter key.
   6496                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   6497             }
   6498             outAttrs.hintText = mHint;
   6499             if (mText instanceof Editable) {
   6500                 InputConnection ic = new EditableInputConnection(this);
   6501                 outAttrs.initialSelStart = getSelectionStart();
   6502                 outAttrs.initialSelEnd = getSelectionEnd();
   6503                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
   6504                 return ic;
   6505             }
   6506         }
   6507         return null;
   6508     }
   6509 
   6510     /**
   6511      * If this TextView contains editable content, extract a portion of it
   6512      * based on the information in <var>request</var> in to <var>outText</var>.
   6513      * @return Returns true if the text was successfully extracted, else false.
   6514      */
   6515     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
   6516         createEditorIfNeeded();
   6517         return mEditor.extractText(request, outText);
   6518     }
   6519 
   6520     /**
   6521      * This is used to remove all style-impacting spans from text before new
   6522      * extracted text is being replaced into it, so that we don't have any
   6523      * lingering spans applied during the replace.
   6524      */
   6525     static void removeParcelableSpans(Spannable spannable, int start, int end) {
   6526         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
   6527         int i = spans.length;
   6528         while (i > 0) {
   6529             i--;
   6530             spannable.removeSpan(spans[i]);
   6531         }
   6532     }
   6533 
   6534     /**
   6535      * Apply to this text view the given extracted text, as previously
   6536      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
   6537      */
   6538     public void setExtractedText(ExtractedText text) {
   6539         Editable content = getEditableText();
   6540         if (text.text != null) {
   6541             if (content == null) {
   6542                 setText(text.text, TextView.BufferType.EDITABLE);
   6543             } else {
   6544                 int start = 0;
   6545                 int end = content.length();
   6546 
   6547                 if (text.partialStartOffset >= 0) {
   6548                     final int N = content.length();
   6549                     start = text.partialStartOffset;
   6550                     if (start > N) start = N;
   6551                     end = text.partialEndOffset;
   6552                     if (end > N) end = N;
   6553                 }
   6554 
   6555                 removeParcelableSpans(content, start, end);
   6556                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
   6557                     if (text.text instanceof Spanned) {
   6558                         // OK to copy spans only.
   6559                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
   6560                                 Object.class, content, start);
   6561                     }
   6562                 } else {
   6563                     content.replace(start, end, text.text);
   6564                 }
   6565             }
   6566         }
   6567 
   6568         // Now set the selection position...  make sure it is in range, to
   6569         // avoid crashes.  If this is a partial update, it is possible that
   6570         // the underlying text may have changed, causing us problems here.
   6571         // Also we just don't want to trust clients to do the right thing.
   6572         Spannable sp = (Spannable)getText();
   6573         final int N = sp.length();
   6574         int start = text.selectionStart;
   6575         if (start < 0) start = 0;
   6576         else if (start > N) start = N;
   6577         int end = text.selectionEnd;
   6578         if (end < 0) end = 0;
   6579         else if (end > N) end = N;
   6580         Selection.setSelection(sp, start, end);
   6581 
   6582         // Finally, update the selection mode.
   6583         if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
   6584             MetaKeyKeyListener.startSelecting(this, sp);
   6585         } else {
   6586             MetaKeyKeyListener.stopSelecting(this, sp);
   6587         }
   6588     }
   6589 
   6590     /**
   6591      * @hide
   6592      */
   6593     public void setExtracting(ExtractedTextRequest req) {
   6594         if (mEditor.mInputMethodState != null) {
   6595             mEditor.mInputMethodState.mExtractedTextRequest = req;
   6596         }
   6597         // This would stop a possible selection mode, but no such mode is started in case
   6598         // extracted mode will start. Some text is selected though, and will trigger an action mode
   6599         // in the extracted view.
   6600         mEditor.hideCursorAndSpanControllers();
   6601         stopTextActionMode();
   6602         if (mEditor.mSelectionModifierCursorController != null) {
   6603             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
   6604         }
   6605     }
   6606 
   6607     /**
   6608      * Called by the framework in response to a text completion from
   6609      * the current input method, provided by it calling
   6610      * {@link InputConnection#commitCompletion
   6611      * InputConnection.commitCompletion()}.  The default implementation does
   6612      * nothing; text views that are supporting auto-completion should override
   6613      * this to do their desired behavior.
   6614      *
   6615      * @param text The auto complete text the user has selected.
   6616      */
   6617     public void onCommitCompletion(CompletionInfo text) {
   6618         // intentionally empty
   6619     }
   6620 
   6621     /**
   6622      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
   6623      * a dictionnary) from the current input method, provided by it calling
   6624      * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
   6625      * implementation flashes the background of the corrected word to provide feedback to the user.
   6626      *
   6627      * @param info The auto correct info about the text that was corrected.
   6628      */
   6629     public void onCommitCorrection(CorrectionInfo info) {
   6630         if (mEditor != null) mEditor.onCommitCorrection(info);
   6631     }
   6632 
   6633     public void beginBatchEdit() {
   6634         if (mEditor != null) mEditor.beginBatchEdit();
   6635     }
   6636 
   6637     public void endBatchEdit() {
   6638         if (mEditor != null) mEditor.endBatchEdit();
   6639     }
   6640 
   6641     /**
   6642      * Called by the framework in response to a request to begin a batch
   6643      * of edit operations through a call to link {@link #beginBatchEdit()}.
   6644      */
   6645     public void onBeginBatchEdit() {
   6646         // intentionally empty
   6647     }
   6648 
   6649     /**
   6650      * Called by the framework in response to a request to end a batch
   6651      * of edit operations through a call to link {@link #endBatchEdit}.
   6652      */
   6653     public void onEndBatchEdit() {
   6654         // intentionally empty
   6655     }
   6656 
   6657     /**
   6658      * Called by the framework in response to a private command from the
   6659      * current method, provided by it calling
   6660      * {@link InputConnection#performPrivateCommand
   6661      * InputConnection.performPrivateCommand()}.
   6662      *
   6663      * @param action The action name of the command.
   6664      * @param data Any additional data for the command.  This may be null.
   6665      * @return Return true if you handled the command, else false.
   6666      */
   6667     public boolean onPrivateIMECommand(String action, Bundle data) {
   6668         return false;
   6669     }
   6670 
   6671     private void nullLayouts() {
   6672         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
   6673             mSavedLayout = (BoringLayout) mLayout;
   6674         }
   6675         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
   6676             mSavedHintLayout = (BoringLayout) mHintLayout;
   6677         }
   6678 
   6679         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
   6680 
   6681         mBoring = mHintBoring = null;
   6682 
   6683         // Since it depends on the value of mLayout
   6684         if (mEditor != null) mEditor.prepareCursorControllers();
   6685     }
   6686 
   6687     /**
   6688      * Make a new Layout based on the already-measured size of the view,
   6689      * on the assumption that it was measured correctly at some point.
   6690      */
   6691     private void assumeLayout() {
   6692         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   6693 
   6694         if (width < 1) {
   6695             width = 0;
   6696         }
   6697 
   6698         int physicalWidth = width;
   6699 
   6700         if (mHorizontallyScrolling) {
   6701             width = VERY_WIDE;
   6702         }
   6703 
   6704         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
   6705                       physicalWidth, false);
   6706     }
   6707 
   6708     private Layout.Alignment getLayoutAlignment() {
   6709         Layout.Alignment alignment;
   6710         switch (getTextAlignment()) {
   6711             case TEXT_ALIGNMENT_GRAVITY:
   6712                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
   6713                     case Gravity.START:
   6714                         alignment = Layout.Alignment.ALIGN_NORMAL;
   6715                         break;
   6716                     case Gravity.END:
   6717                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
   6718                         break;
   6719                     case Gravity.LEFT:
   6720                         alignment = Layout.Alignment.ALIGN_LEFT;
   6721                         break;
   6722                     case Gravity.RIGHT:
   6723                         alignment = Layout.Alignment.ALIGN_RIGHT;
   6724                         break;
   6725                     case Gravity.CENTER_HORIZONTAL:
   6726                         alignment = Layout.Alignment.ALIGN_CENTER;
   6727                         break;
   6728                     default:
   6729                         alignment = Layout.Alignment.ALIGN_NORMAL;
   6730                         break;
   6731                 }
   6732                 break;
   6733             case TEXT_ALIGNMENT_TEXT_START:
   6734                 alignment = Layout.Alignment.ALIGN_NORMAL;
   6735                 break;
   6736             case TEXT_ALIGNMENT_TEXT_END:
   6737                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
   6738                 break;
   6739             case TEXT_ALIGNMENT_CENTER:
   6740                 alignment = Layout.Alignment.ALIGN_CENTER;
   6741                 break;
   6742             case TEXT_ALIGNMENT_VIEW_START:
   6743                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
   6744                         Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
   6745                 break;
   6746             case TEXT_ALIGNMENT_VIEW_END:
   6747                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
   6748                         Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
   6749                 break;
   6750             case TEXT_ALIGNMENT_INHERIT:
   6751                 // This should never happen as we have already resolved the text alignment
   6752                 // but better safe than sorry so we just fall through
   6753             default:
   6754                 alignment = Layout.Alignment.ALIGN_NORMAL;
   6755                 break;
   6756         }
   6757         return alignment;
   6758     }
   6759 
   6760     /**
   6761      * The width passed in is now the desired layout width,
   6762      * not the full view width with padding.
   6763      * {@hide}
   6764      */
   6765     protected void makeNewLayout(int wantWidth, int hintWidth,
   6766                                  BoringLayout.Metrics boring,
   6767                                  BoringLayout.Metrics hintBoring,
   6768                                  int ellipsisWidth, boolean bringIntoView) {
   6769         stopMarquee();
   6770 
   6771         // Update "old" cached values
   6772         mOldMaximum = mMaximum;
   6773         mOldMaxMode = mMaxMode;
   6774 
   6775         mHighlightPathBogus = true;
   6776 
   6777         if (wantWidth < 0) {
   6778             wantWidth = 0;
   6779         }
   6780         if (hintWidth < 0) {
   6781             hintWidth = 0;
   6782         }
   6783 
   6784         Layout.Alignment alignment = getLayoutAlignment();
   6785         final boolean testDirChange = mSingleLine && mLayout != null &&
   6786             (alignment == Layout.Alignment.ALIGN_NORMAL ||
   6787              alignment == Layout.Alignment.ALIGN_OPPOSITE);
   6788         int oldDir = 0;
   6789         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
   6790         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
   6791         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
   6792                 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
   6793         TruncateAt effectiveEllipsize = mEllipsize;
   6794         if (mEllipsize == TruncateAt.MARQUEE &&
   6795                 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   6796             effectiveEllipsize = TruncateAt.END_SMALL;
   6797         }
   6798 
   6799         if (mTextDir == null) {
   6800             mTextDir = getTextDirectionHeuristic();
   6801         }
   6802 
   6803         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
   6804                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
   6805         if (switchEllipsize) {
   6806             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
   6807                     TruncateAt.END : TruncateAt.MARQUEE;
   6808             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
   6809                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
   6810         }
   6811 
   6812         shouldEllipsize = mEllipsize != null;
   6813         mHintLayout = null;
   6814 
   6815         if (mHint != null) {
   6816             if (shouldEllipsize) hintWidth = wantWidth;
   6817 
   6818             if (hintBoring == UNKNOWN_BORING) {
   6819                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
   6820                                                    mHintBoring);
   6821                 if (hintBoring != null) {
   6822                     mHintBoring = hintBoring;
   6823                 }
   6824             }
   6825 
   6826             if (hintBoring != null) {
   6827                 if (hintBoring.width <= hintWidth &&
   6828                     (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
   6829                     if (mSavedHintLayout != null) {
   6830                         mHintLayout = mSavedHintLayout.
   6831                                 replaceOrMake(mHint, mTextPaint,
   6832                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   6833                                 hintBoring, mIncludePad);
   6834                     } else {
   6835                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   6836                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   6837                                 hintBoring, mIncludePad);
   6838                     }
   6839 
   6840                     mSavedHintLayout = (BoringLayout) mHintLayout;
   6841                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
   6842                     if (mSavedHintLayout != null) {
   6843                         mHintLayout = mSavedHintLayout.
   6844                                 replaceOrMake(mHint, mTextPaint,
   6845                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   6846                                 hintBoring, mIncludePad, mEllipsize,
   6847                                 ellipsisWidth);
   6848                     } else {
   6849                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   6850                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   6851                                 hintBoring, mIncludePad, mEllipsize,
   6852                                 ellipsisWidth);
   6853                     }
   6854                 }
   6855             }
   6856             // TODO: code duplication with makeSingleLayout()
   6857             if (mHintLayout == null) {
   6858                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
   6859                         mHint.length(), mTextPaint, hintWidth)
   6860                         .setAlignment(alignment)
   6861                         .setTextDirection(mTextDir)
   6862                         .setLineSpacing(mSpacingAdd, mSpacingMult)
   6863                         .setIncludePad(mIncludePad)
   6864                         .setBreakStrategy(mBreakStrategy)
   6865                         .setHyphenationFrequency(mHyphenationFrequency);
   6866                 if (shouldEllipsize) {
   6867                     builder.setEllipsize(mEllipsize)
   6868                             .setEllipsizedWidth(ellipsisWidth)
   6869                             .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
   6870                 }
   6871                 mHintLayout = builder.build();
   6872             }
   6873         }
   6874 
   6875         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
   6876             registerForPreDraw();
   6877         }
   6878 
   6879         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   6880             if (!compressText(ellipsisWidth)) {
   6881                 final int height = mLayoutParams.height;
   6882                 // If the size of the view does not depend on the size of the text, try to
   6883                 // start the marquee immediately
   6884                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
   6885                     startMarquee();
   6886                 } else {
   6887                     // Defer the start of the marquee until we know our width (see setFrame())
   6888                     mRestartMarquee = true;
   6889                 }
   6890             }
   6891         }
   6892 
   6893         // CursorControllers need a non-null mLayout
   6894         if (mEditor != null) mEditor.prepareCursorControllers();
   6895     }
   6896 
   6897     /**
   6898      * @hide
   6899      */
   6900     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
   6901             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
   6902             boolean useSaved) {
   6903         Layout result = null;
   6904         if (mText instanceof Spannable) {
   6905             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
   6906                     alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
   6907                     mBreakStrategy, mHyphenationFrequency,
   6908                     getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
   6909         } else {
   6910             if (boring == UNKNOWN_BORING) {
   6911                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
   6912                 if (boring != null) {
   6913                     mBoring = boring;
   6914                 }
   6915             }
   6916 
   6917             if (boring != null) {
   6918                 if (boring.width <= wantWidth &&
   6919                         (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
   6920                     if (useSaved && mSavedLayout != null) {
   6921                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
   6922                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   6923                                 boring, mIncludePad);
   6924                     } else {
   6925                         result = BoringLayout.make(mTransformed, mTextPaint,
   6926                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   6927                                 boring, mIncludePad);
   6928                     }
   6929 
   6930                     if (useSaved) {
   6931                         mSavedLayout = (BoringLayout) result;
   6932                     }
   6933                 } else if (shouldEllipsize && boring.width <= wantWidth) {
   6934                     if (useSaved && mSavedLayout != null) {
   6935                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
   6936                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   6937                                 boring, mIncludePad, effectiveEllipsize,
   6938                                 ellipsisWidth);
   6939                     } else {
   6940                         result = BoringLayout.make(mTransformed, mTextPaint,
   6941                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   6942                                 boring, mIncludePad, effectiveEllipsize,
   6943                                 ellipsisWidth);
   6944                     }
   6945                 }
   6946             }
   6947         }
   6948         if (result == null) {
   6949             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
   6950                     0, mTransformed.length(), mTextPaint, wantWidth)
   6951                     .setAlignment(alignment)
   6952                     .setTextDirection(mTextDir)
   6953                     .setLineSpacing(mSpacingAdd, mSpacingMult)
   6954                     .setIncludePad(mIncludePad)
   6955                     .setBreakStrategy(mBreakStrategy)
   6956                     .setHyphenationFrequency(mHyphenationFrequency);
   6957             if (shouldEllipsize) {
   6958                 builder.setEllipsize(effectiveEllipsize)
   6959                         .setEllipsizedWidth(ellipsisWidth)
   6960                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
   6961             }
   6962             // TODO: explore always setting maxLines
   6963             result = builder.build();
   6964         }
   6965         return result;
   6966     }
   6967 
   6968     private boolean compressText(float width) {
   6969         if (isHardwareAccelerated()) return false;
   6970 
   6971         // Only compress the text if it hasn't been compressed by the previous pass
   6972         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
   6973                 mTextPaint.getTextScaleX() == 1.0f) {
   6974             final float textWidth = mLayout.getLineWidth(0);
   6975             final float overflow = (textWidth + 1.0f - width) / width;
   6976             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
   6977                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
   6978                 post(new Runnable() {
   6979                     public void run() {
   6980                         requestLayout();
   6981                     }
   6982                 });
   6983                 return true;
   6984             }
   6985         }
   6986 
   6987         return false;
   6988     }
   6989 
   6990     private static int desired(Layout layout) {
   6991         int n = layout.getLineCount();
   6992         CharSequence text = layout.getText();
   6993         float max = 0;
   6994 
   6995         // if any line was wrapped, we can't use it.
   6996         // but it's ok for the last line not to have a newline
   6997 
   6998         for (int i = 0; i < n - 1; i++) {
   6999             if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
   7000                 return -1;
   7001         }
   7002 
   7003         for (int i = 0; i < n; i++) {
   7004             max = Math.max(max, layout.getLineWidth(i));
   7005         }
   7006 
   7007         return (int) Math.ceil(max);
   7008     }
   7009 
   7010     /**
   7011      * Set whether the TextView includes extra top and bottom padding to make
   7012      * room for accents that go above the normal ascent and descent.
   7013      * The default is true.
   7014      *
   7015      * @see #getIncludeFontPadding()
   7016      *
   7017      * @attr ref android.R.styleable#TextView_includeFontPadding
   7018      */
   7019     public void setIncludeFontPadding(boolean includepad) {
   7020         if (mIncludePad != includepad) {
   7021             mIncludePad = includepad;
   7022 
   7023             if (mLayout != null) {
   7024                 nullLayouts();
   7025                 requestLayout();
   7026                 invalidate();
   7027             }
   7028         }
   7029     }
   7030 
   7031     /**
   7032      * Gets whether the TextView includes extra top and bottom padding to make
   7033      * room for accents that go above the normal ascent and descent.
   7034      *
   7035      * @see #setIncludeFontPadding(boolean)
   7036      *
   7037      * @attr ref android.R.styleable#TextView_includeFontPadding
   7038      */
   7039     public boolean getIncludeFontPadding() {
   7040         return mIncludePad;
   7041     }
   7042 
   7043     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
   7044 
   7045     @Override
   7046     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   7047         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   7048         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   7049         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   7050         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   7051 
   7052         int width;
   7053         int height;
   7054 
   7055         BoringLayout.Metrics boring = UNKNOWN_BORING;
   7056         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
   7057 
   7058         if (mTextDir == null) {
   7059             mTextDir = getTextDirectionHeuristic();
   7060         }
   7061 
   7062         int des = -1;
   7063         boolean fromexisting = false;
   7064 
   7065         if (widthMode == MeasureSpec.EXACTLY) {
   7066             // Parent has told us how big to be. So be it.
   7067             width = widthSize;
   7068         } else {
   7069             if (mLayout != null && mEllipsize == null) {
   7070                 des = desired(mLayout);
   7071             }
   7072 
   7073             if (des < 0) {
   7074                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
   7075                 if (boring != null) {
   7076                     mBoring = boring;
   7077                 }
   7078             } else {
   7079                 fromexisting = true;
   7080             }
   7081 
   7082             if (boring == null || boring == UNKNOWN_BORING) {
   7083                 if (des < 0) {
   7084                     des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
   7085                 }
   7086                 width = des;
   7087             } else {
   7088                 width = boring.width;
   7089             }
   7090 
   7091             final Drawables dr = mDrawables;
   7092             if (dr != null) {
   7093                 width = Math.max(width, dr.mDrawableWidthTop);
   7094                 width = Math.max(width, dr.mDrawableWidthBottom);
   7095             }
   7096 
   7097             if (mHint != null) {
   7098                 int hintDes = -1;
   7099                 int hintWidth;
   7100 
   7101                 if (mHintLayout != null && mEllipsize == null) {
   7102                     hintDes = desired(mHintLayout);
   7103                 }
   7104 
   7105                 if (hintDes < 0) {
   7106                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
   7107                     if (hintBoring != null) {
   7108                         mHintBoring = hintBoring;
   7109                     }
   7110                 }
   7111 
   7112                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
   7113                     if (hintDes < 0) {
   7114                         hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
   7115                     }
   7116                     hintWidth = hintDes;
   7117                 } else {
   7118                     hintWidth = hintBoring.width;
   7119                 }
   7120 
   7121                 if (hintWidth > width) {
   7122                     width = hintWidth;
   7123                 }
   7124             }
   7125 
   7126             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
   7127 
   7128             if (mMaxWidthMode == EMS) {
   7129                 width = Math.min(width, mMaxWidth * getLineHeight());
   7130             } else {
   7131                 width = Math.min(width, mMaxWidth);
   7132             }
   7133 
   7134             if (mMinWidthMode == EMS) {
   7135                 width = Math.max(width, mMinWidth * getLineHeight());
   7136             } else {
   7137                 width = Math.max(width, mMinWidth);
   7138             }
   7139 
   7140             // Check against our minimum width
   7141             width = Math.max(width, getSuggestedMinimumWidth());
   7142 
   7143             if (widthMode == MeasureSpec.AT_MOST) {
   7144                 width = Math.min(widthSize, width);
   7145             }
   7146         }
   7147 
   7148         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
   7149         int unpaddedWidth = want;
   7150 
   7151         if (mHorizontallyScrolling) want = VERY_WIDE;
   7152 
   7153         int hintWant = want;
   7154         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
   7155 
   7156         if (mLayout == null) {
   7157             makeNewLayout(want, hintWant, boring, hintBoring,
   7158                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   7159         } else {
   7160             final boolean layoutChanged = (mLayout.getWidth() != want) ||
   7161                     (hintWidth != hintWant) ||
   7162                     (mLayout.getEllipsizedWidth() !=
   7163                             width - getCompoundPaddingLeft() - getCompoundPaddingRight());
   7164 
   7165             final boolean widthChanged = (mHint == null) &&
   7166                     (mEllipsize == null) &&
   7167                     (want > mLayout.getWidth()) &&
   7168                     (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
   7169 
   7170             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
   7171 
   7172             if (layoutChanged || maximumChanged) {
   7173                 if (!maximumChanged && widthChanged) {
   7174                     mLayout.increaseWidthTo(want);
   7175                 } else {
   7176                     makeNewLayout(want, hintWant, boring, hintBoring,
   7177                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   7178                 }
   7179             } else {
   7180                 // Nothing has changed
   7181             }
   7182         }
   7183 
   7184         if (heightMode == MeasureSpec.EXACTLY) {
   7185             // Parent has told us how big to be. So be it.
   7186             height = heightSize;
   7187             mDesiredHeightAtMeasure = -1;
   7188         } else {
   7189             int desired = getDesiredHeight();
   7190 
   7191             height = desired;
   7192             mDesiredHeightAtMeasure = desired;
   7193 
   7194             if (heightMode == MeasureSpec.AT_MOST) {
   7195                 height = Math.min(desired, heightSize);
   7196             }
   7197         }
   7198 
   7199         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
   7200         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
   7201             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
   7202         }
   7203 
   7204         /*
   7205          * We didn't let makeNewLayout() register to bring the cursor into view,
   7206          * so do it here if there is any possibility that it is needed.
   7207          */
   7208         if (mMovement != null ||
   7209             mLayout.getWidth() > unpaddedWidth ||
   7210             mLayout.getHeight() > unpaddedHeight) {
   7211             registerForPreDraw();
   7212         } else {
   7213             scrollTo(0, 0);
   7214         }
   7215 
   7216         setMeasuredDimension(width, height);
   7217     }
   7218 
   7219     private int getDesiredHeight() {
   7220         return Math.max(
   7221                 getDesiredHeight(mLayout, true),
   7222                 getDesiredHeight(mHintLayout, mEllipsize != null));
   7223     }
   7224 
   7225     private int getDesiredHeight(Layout layout, boolean cap) {
   7226         if (layout == null) {
   7227             return 0;
   7228         }
   7229 
   7230         int linecount = layout.getLineCount();
   7231         int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
   7232         int desired = layout.getLineTop(linecount);
   7233 
   7234         final Drawables dr = mDrawables;
   7235         if (dr != null) {
   7236             desired = Math.max(desired, dr.mDrawableHeightLeft);
   7237             desired = Math.max(desired, dr.mDrawableHeightRight);
   7238         }
   7239 
   7240         desired += pad;
   7241 
   7242         if (mMaxMode == LINES) {
   7243             /*
   7244              * Don't cap the hint to a certain number of lines.
   7245              * (Do cap it, though, if we have a maximum pixel height.)
   7246              */
   7247             if (cap) {
   7248                 if (linecount > mMaximum) {
   7249                     desired = layout.getLineTop(mMaximum);
   7250 
   7251                     if (dr != null) {
   7252                         desired = Math.max(desired, dr.mDrawableHeightLeft);
   7253                         desired = Math.max(desired, dr.mDrawableHeightRight);
   7254                     }
   7255 
   7256                     desired += pad;
   7257                     linecount = mMaximum;
   7258                 }
   7259             }
   7260         } else {
   7261             desired = Math.min(desired, mMaximum);
   7262         }
   7263 
   7264         if (mMinMode == LINES) {
   7265             if (linecount < mMinimum) {
   7266                 desired += getLineHeight() * (mMinimum - linecount);
   7267             }
   7268         } else {
   7269             desired = Math.max(desired, mMinimum);
   7270         }
   7271 
   7272         // Check against our minimum height
   7273         desired = Math.max(desired, getSuggestedMinimumHeight());
   7274 
   7275         return desired;
   7276     }
   7277 
   7278     /**
   7279      * Check whether a change to the existing text layout requires a
   7280      * new view layout.
   7281      */
   7282     private void checkForResize() {
   7283         boolean sizeChanged = false;
   7284 
   7285         if (mLayout != null) {
   7286             // Check if our width changed
   7287             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
   7288                 sizeChanged = true;
   7289                 invalidate();
   7290             }
   7291 
   7292             // Check if our height changed
   7293             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
   7294                 int desiredHeight = getDesiredHeight();
   7295 
   7296                 if (desiredHeight != this.getHeight()) {
   7297                     sizeChanged = true;
   7298                 }
   7299             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
   7300                 if (mDesiredHeightAtMeasure >= 0) {
   7301                     int desiredHeight = getDesiredHeight();
   7302 
   7303                     if (desiredHeight != mDesiredHeightAtMeasure) {
   7304                         sizeChanged = true;
   7305                     }
   7306                 }
   7307             }
   7308         }
   7309 
   7310         if (sizeChanged) {
   7311             requestLayout();
   7312             // caller will have already invalidated
   7313         }
   7314     }
   7315 
   7316     /**
   7317      * Check whether entirely new text requires a new view layout
   7318      * or merely a new text layout.
   7319      */
   7320     private void checkForRelayout() {
   7321         // If we have a fixed width, we can just swap in a new text layout
   7322         // if the text height stays the same or if the view height is fixed.
   7323 
   7324         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
   7325                 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
   7326                 (mHint == null || mHintLayout != null) &&
   7327                 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
   7328             // Static width, so try making a new text layout.
   7329 
   7330             int oldht = mLayout.getHeight();
   7331             int want = mLayout.getWidth();
   7332             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   7333 
   7334             /*
   7335              * No need to bring the text into view, since the size is not
   7336              * changing (unless we do the requestLayout(), in which case it
   7337              * will happen at measure).
   7338              */
   7339             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   7340                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
   7341                           false);
   7342 
   7343             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
   7344                 // In a fixed-height view, so use our new text layout.
   7345                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
   7346                     mLayoutParams.height != LayoutParams.MATCH_PARENT) {
   7347                     invalidate();
   7348                     return;
   7349                 }
   7350 
   7351                 // Dynamic height, but height has stayed the same,
   7352                 // so use our new text layout.
   7353                 if (mLayout.getHeight() == oldht &&
   7354                     (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
   7355                     invalidate();
   7356                     return;
   7357                 }
   7358             }
   7359 
   7360             // We lose: the height has changed and we have a dynamic height.
   7361             // Request a new view layout using our new text layout.
   7362             requestLayout();
   7363             invalidate();
   7364         } else {
   7365             // Dynamic width, so we have no choice but to request a new
   7366             // view layout with a new text layout.
   7367             nullLayouts();
   7368             requestLayout();
   7369             invalidate();
   7370         }
   7371     }
   7372 
   7373     @Override
   7374     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   7375         super.onLayout(changed, left, top, right, bottom);
   7376         if (mDeferScroll >= 0) {
   7377             int curs = mDeferScroll;
   7378             mDeferScroll = -1;
   7379             bringPointIntoView(Math.min(curs, mText.length()));
   7380         }
   7381     }
   7382 
   7383     private boolean isShowingHint() {
   7384         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
   7385     }
   7386 
   7387     /**
   7388      * Returns true if anything changed.
   7389      */
   7390     private boolean bringTextIntoView() {
   7391         Layout layout = isShowingHint() ? mHintLayout : mLayout;
   7392         int line = 0;
   7393         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   7394             line = layout.getLineCount() - 1;
   7395         }
   7396 
   7397         Layout.Alignment a = layout.getParagraphAlignment(line);
   7398         int dir = layout.getParagraphDirection(line);
   7399         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   7400         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   7401         int ht = layout.getHeight();
   7402 
   7403         int scrollx, scrolly;
   7404 
   7405         // Convert to left, center, or right alignment.
   7406         if (a == Layout.Alignment.ALIGN_NORMAL) {
   7407             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
   7408                 Layout.Alignment.ALIGN_RIGHT;
   7409         } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
   7410             a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
   7411                 Layout.Alignment.ALIGN_LEFT;
   7412         }
   7413 
   7414         if (a == Layout.Alignment.ALIGN_CENTER) {
   7415             /*
   7416              * Keep centered if possible, or, if it is too wide to fit,
   7417              * keep leading edge in view.
   7418              */
   7419 
   7420             int left = (int) Math.floor(layout.getLineLeft(line));
   7421             int right = (int) Math.ceil(layout.getLineRight(line));
   7422 
   7423             if (right - left < hspace) {
   7424                 scrollx = (right + left) / 2 - hspace / 2;
   7425             } else {
   7426                 if (dir < 0) {
   7427                     scrollx = right - hspace;
   7428                 } else {
   7429                     scrollx = left;
   7430                 }
   7431             }
   7432         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
   7433             int right = (int) Math.ceil(layout.getLineRight(line));
   7434             scrollx = right - hspace;
   7435         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
   7436             scrollx = (int) Math.floor(layout.getLineLeft(line));
   7437         }
   7438 
   7439         if (ht < vspace) {
   7440             scrolly = 0;
   7441         } else {
   7442             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   7443                 scrolly = ht - vspace;
   7444             } else {
   7445                 scrolly = 0;
   7446             }
   7447         }
   7448 
   7449         if (scrollx != mScrollX || scrolly != mScrollY) {
   7450             scrollTo(scrollx, scrolly);
   7451             return true;
   7452         } else {
   7453             return false;
   7454         }
   7455     }
   7456 
   7457     /**
   7458      * Move the point, specified by the offset, into the view if it is needed.
   7459      * This has to be called after layout. Returns true if anything changed.
   7460      */
   7461     public boolean bringPointIntoView(int offset) {
   7462         if (isLayoutRequested()) {
   7463             mDeferScroll = offset;
   7464             return false;
   7465         }
   7466         boolean changed = false;
   7467 
   7468         Layout layout = isShowingHint() ? mHintLayout: mLayout;
   7469 
   7470         if (layout == null) return changed;
   7471 
   7472         int line = layout.getLineForOffset(offset);
   7473 
   7474         int grav;
   7475 
   7476         switch (layout.getParagraphAlignment(line)) {
   7477             case ALIGN_LEFT:
   7478                 grav = 1;
   7479                 break;
   7480             case ALIGN_RIGHT:
   7481                 grav = -1;
   7482                 break;
   7483             case ALIGN_NORMAL:
   7484                 grav = layout.getParagraphDirection(line);
   7485                 break;
   7486             case ALIGN_OPPOSITE:
   7487                 grav = -layout.getParagraphDirection(line);
   7488                 break;
   7489             case ALIGN_CENTER:
   7490             default:
   7491                 grav = 0;
   7492                 break;
   7493         }
   7494 
   7495         // We only want to clamp the cursor to fit within the layout width
   7496         // in left-to-right modes, because in a right to left alignment,
   7497         // we want to scroll to keep the line-right on the screen, as other
   7498         // lines are likely to have text flush with the right margin, which
   7499         // we want to keep visible.
   7500         // A better long-term solution would probably be to measure both
   7501         // the full line and a blank-trimmed version, and, for example, use
   7502         // the latter measurement for centering and right alignment, but for
   7503         // the time being we only implement the cursor clamping in left to
   7504         // right where it is most likely to be annoying.
   7505         final boolean clamped = grav > 0;
   7506         // FIXME: Is it okay to truncate this, or should we round?
   7507         final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
   7508         final int top = layout.getLineTop(line);
   7509         final int bottom = layout.getLineTop(line + 1);
   7510 
   7511         int left = (int) Math.floor(layout.getLineLeft(line));
   7512         int right = (int) Math.ceil(layout.getLineRight(line));
   7513         int ht = layout.getHeight();
   7514 
   7515         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   7516         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   7517         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
   7518             // If cursor has been clamped, make sure we don't scroll.
   7519             right = Math.max(x, left + hspace);
   7520         }
   7521 
   7522         int hslack = (bottom - top) / 2;
   7523         int vslack = hslack;
   7524 
   7525         if (vslack > vspace / 4)
   7526             vslack = vspace / 4;
   7527         if (hslack > hspace / 4)
   7528             hslack = hspace / 4;
   7529 
   7530         int hs = mScrollX;
   7531         int vs = mScrollY;
   7532 
   7533         if (top - vs < vslack)
   7534             vs = top - vslack;
   7535         if (bottom - vs > vspace - vslack)
   7536             vs = bottom - (vspace - vslack);
   7537         if (ht - vs < vspace)
   7538             vs = ht - vspace;
   7539         if (0 - vs > 0)
   7540             vs = 0;
   7541 
   7542         if (grav != 0) {
   7543             if (x - hs < hslack) {
   7544                 hs = x - hslack;
   7545             }
   7546             if (x - hs > hspace - hslack) {
   7547                 hs = x - (hspace - hslack);
   7548             }
   7549         }
   7550 
   7551         if (grav < 0) {
   7552             if (left - hs > 0)
   7553                 hs = left;
   7554             if (right - hs < hspace)
   7555                 hs = right - hspace;
   7556         } else if (grav > 0) {
   7557             if (right - hs < hspace)
   7558                 hs = right - hspace;
   7559             if (left - hs > 0)
   7560                 hs = left;
   7561         } else /* grav == 0 */ {
   7562             if (right - left <= hspace) {
   7563                 /*
   7564                  * If the entire text fits, center it exactly.
   7565                  */
   7566                 hs = left - (hspace - (right - left)) / 2;
   7567             } else if (x > right - hslack) {
   7568                 /*
   7569                  * If we are near the right edge, keep the right edge
   7570                  * at the edge of the view.
   7571                  */
   7572                 hs = right - hspace;
   7573             } else if (x < left + hslack) {
   7574                 /*
   7575                  * If we are near the left edge, keep the left edge
   7576                  * at the edge of the view.
   7577                  */
   7578                 hs = left;
   7579             } else if (left > hs) {
   7580                 /*
   7581                  * Is there whitespace visible at the left?  Fix it if so.
   7582                  */
   7583                 hs = left;
   7584             } else if (right < hs + hspace) {
   7585                 /*
   7586                  * Is there whitespace visible at the right?  Fix it if so.
   7587                  */
   7588                 hs = right - hspace;
   7589             } else {
   7590                 /*
   7591                  * Otherwise, float as needed.
   7592                  */
   7593                 if (x - hs < hslack) {
   7594                     hs = x - hslack;
   7595                 }
   7596                 if (x - hs > hspace - hslack) {
   7597                     hs = x - (hspace - hslack);
   7598                 }
   7599             }
   7600         }
   7601 
   7602         if (hs != mScrollX || vs != mScrollY) {
   7603             if (mScroller == null) {
   7604                 scrollTo(hs, vs);
   7605             } else {
   7606                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
   7607                 int dx = hs - mScrollX;
   7608                 int dy = vs - mScrollY;
   7609 
   7610                 if (duration > ANIMATED_SCROLL_GAP) {
   7611                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
   7612                     awakenScrollBars(mScroller.getDuration());
   7613                     invalidate();
   7614                 } else {
   7615                     if (!mScroller.isFinished()) {
   7616                         mScroller.abortAnimation();
   7617                     }
   7618 
   7619                     scrollBy(dx, dy);
   7620                 }
   7621 
   7622                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
   7623             }
   7624 
   7625             changed = true;
   7626         }
   7627 
   7628         if (isFocused()) {
   7629             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
   7630             // requestRectangleOnScreen() is in terms of content coordinates.
   7631 
   7632             // The offsets here are to ensure the rectangle we are using is
   7633             // within our view bounds, in case the cursor is on the far left
   7634             // or right.  If it isn't withing the bounds, then this request
   7635             // will be ignored.
   7636             if (mTempRect == null) mTempRect = new Rect();
   7637             mTempRect.set(x - 2, top, x + 2, bottom);
   7638             getInterestingRect(mTempRect, line);
   7639             mTempRect.offset(mScrollX, mScrollY);
   7640 
   7641             if (requestRectangleOnScreen(mTempRect)) {
   7642                 changed = true;
   7643             }
   7644         }
   7645 
   7646         return changed;
   7647     }
   7648 
   7649     /**
   7650      * Move the cursor, if needed, so that it is at an offset that is visible
   7651      * to the user.  This will not move the cursor if it represents more than
   7652      * one character (a selection range).  This will only work if the
   7653      * TextView contains spannable text; otherwise it will do nothing.
   7654      *
   7655      * @return True if the cursor was actually moved, false otherwise.
   7656      */
   7657     public boolean moveCursorToVisibleOffset() {
   7658         if (!(mText instanceof Spannable)) {
   7659             return false;
   7660         }
   7661         int start = getSelectionStart();
   7662         int end = getSelectionEnd();
   7663         if (start != end) {
   7664             return false;
   7665         }
   7666 
   7667         // First: make sure the line is visible on screen:
   7668 
   7669         int line = mLayout.getLineForOffset(start);
   7670 
   7671         final int top = mLayout.getLineTop(line);
   7672         final int bottom = mLayout.getLineTop(line + 1);
   7673         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   7674         int vslack = (bottom - top) / 2;
   7675         if (vslack > vspace / 4)
   7676             vslack = vspace / 4;
   7677         final int vs = mScrollY;
   7678 
   7679         if (top < (vs+vslack)) {
   7680             line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
   7681         } else if (bottom > (vspace+vs-vslack)) {
   7682             line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
   7683         }
   7684 
   7685         // Next: make sure the character is visible on screen:
   7686 
   7687         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   7688         final int hs = mScrollX;
   7689         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
   7690         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
   7691 
   7692         // line might contain bidirectional text
   7693         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
   7694         final int highChar = leftChar > rightChar ? leftChar : rightChar;
   7695 
   7696         int newStart = start;
   7697         if (newStart < lowChar) {
   7698             newStart = lowChar;
   7699         } else if (newStart > highChar) {
   7700             newStart = highChar;
   7701         }
   7702 
   7703         if (newStart != start) {
   7704             Selection.setSelection((Spannable)mText, newStart);
   7705             return true;
   7706         }
   7707 
   7708         return false;
   7709     }
   7710 
   7711     @Override
   7712     public void computeScroll() {
   7713         if (mScroller != null) {
   7714             if (mScroller.computeScrollOffset()) {
   7715                 mScrollX = mScroller.getCurrX();
   7716                 mScrollY = mScroller.getCurrY();
   7717                 invalidateParentCaches();
   7718                 postInvalidate();  // So we draw again
   7719             }
   7720         }
   7721     }
   7722 
   7723     private void getInterestingRect(Rect r, int line) {
   7724         convertFromViewportToContentCoordinates(r);
   7725 
   7726         // Rectangle can can be expanded on first and last line to take
   7727         // padding into account.
   7728         // TODO Take left/right padding into account too?
   7729         if (line == 0) r.top -= getExtendedPaddingTop();
   7730         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
   7731     }
   7732 
   7733     private void convertFromViewportToContentCoordinates(Rect r) {
   7734         final int horizontalOffset = viewportToContentHorizontalOffset();
   7735         r.left += horizontalOffset;
   7736         r.right += horizontalOffset;
   7737 
   7738         final int verticalOffset = viewportToContentVerticalOffset();
   7739         r.top += verticalOffset;
   7740         r.bottom += verticalOffset;
   7741     }
   7742 
   7743     int viewportToContentHorizontalOffset() {
   7744         return getCompoundPaddingLeft() - mScrollX;
   7745     }
   7746 
   7747     int viewportToContentVerticalOffset() {
   7748         int offset = getExtendedPaddingTop() - mScrollY;
   7749         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   7750             offset += getVerticalOffset(false);
   7751         }
   7752         return offset;
   7753     }
   7754 
   7755     @Override
   7756     public void debug(int depth) {
   7757         super.debug(depth);
   7758 
   7759         String output = debugIndent(depth);
   7760         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
   7761                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
   7762                 + "} ";
   7763 
   7764         if (mText != null) {
   7765 
   7766             output += "mText=\"" + mText + "\" ";
   7767             if (mLayout != null) {
   7768                 output += "mLayout width=" + mLayout.getWidth()
   7769                         + " height=" + mLayout.getHeight();
   7770             }
   7771         } else {
   7772             output += "mText=NULL";
   7773         }
   7774         Log.d(VIEW_LOG_TAG, output);
   7775     }
   7776 
   7777     /**
   7778      * Convenience for {@link Selection#getSelectionStart}.
   7779      */
   7780     @ViewDebug.ExportedProperty(category = "text")
   7781     public int getSelectionStart() {
   7782         return Selection.getSelectionStart(getText());
   7783     }
   7784 
   7785     /**
   7786      * Convenience for {@link Selection#getSelectionEnd}.
   7787      */
   7788     @ViewDebug.ExportedProperty(category = "text")
   7789     public int getSelectionEnd() {
   7790         return Selection.getSelectionEnd(getText());
   7791     }
   7792 
   7793     /**
   7794      * Return true iff there is a selection inside this text view.
   7795      */
   7796     public boolean hasSelection() {
   7797         final int selectionStart = getSelectionStart();
   7798         final int selectionEnd = getSelectionEnd();
   7799 
   7800         return selectionStart >= 0 && selectionStart != selectionEnd;
   7801     }
   7802 
   7803     String getSelectedText() {
   7804         if (!hasSelection()) {
   7805             return null;
   7806         }
   7807 
   7808         final int start = getSelectionStart();
   7809         final int end = getSelectionEnd();
   7810         return String.valueOf(
   7811                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
   7812     }
   7813 
   7814     /**
   7815      * Sets the properties of this field (lines, horizontally scrolling,
   7816      * transformation method) to be for a single-line input.
   7817      *
   7818      * @attr ref android.R.styleable#TextView_singleLine
   7819      */
   7820     public void setSingleLine() {
   7821         setSingleLine(true);
   7822     }
   7823 
   7824     /**
   7825      * Sets the properties of this field to transform input to ALL CAPS
   7826      * display. This may use a "small caps" formatting if available.
   7827      * This setting will be ignored if this field is editable or selectable.
   7828      *
   7829      * This call replaces the current transformation method. Disabling this
   7830      * will not necessarily restore the previous behavior from before this
   7831      * was enabled.
   7832      *
   7833      * @see #setTransformationMethod(TransformationMethod)
   7834      * @attr ref android.R.styleable#TextView_textAllCaps
   7835      */
   7836     public void setAllCaps(boolean allCaps) {
   7837         if (allCaps) {
   7838             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
   7839         } else {
   7840             setTransformationMethod(null);
   7841         }
   7842     }
   7843 
   7844     /**
   7845      * If true, sets the properties of this field (number of lines, horizontally scrolling,
   7846      * transformation method) to be for a single-line input; if false, restores these to the default
   7847      * conditions.
   7848      *
   7849      * Note that the default conditions are not necessarily those that were in effect prior this
   7850      * method, and you may want to reset these properties to your custom values.
   7851      *
   7852      * @attr ref android.R.styleable#TextView_singleLine
   7853      */
   7854     @android.view.RemotableViewMethod
   7855     public void setSingleLine(boolean singleLine) {
   7856         // Could be used, but may break backward compatibility.
   7857         // if (mSingleLine == singleLine) return;
   7858         setInputTypeSingleLine(singleLine);
   7859         applySingleLine(singleLine, true, true);
   7860     }
   7861 
   7862     /**
   7863      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
   7864      * @param singleLine
   7865      */
   7866     private void setInputTypeSingleLine(boolean singleLine) {
   7867         if (mEditor != null &&
   7868                 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
   7869             if (singleLine) {
   7870                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   7871             } else {
   7872                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   7873             }
   7874         }
   7875     }
   7876 
   7877     private void applySingleLine(boolean singleLine, boolean applyTransformation,
   7878             boolean changeMaxLines) {
   7879         mSingleLine = singleLine;
   7880         if (singleLine) {
   7881             setLines(1);
   7882             setHorizontallyScrolling(true);
   7883             if (applyTransformation) {
   7884                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
   7885             }
   7886         } else {
   7887             if (changeMaxLines) {
   7888                 setMaxLines(Integer.MAX_VALUE);
   7889             }
   7890             setHorizontallyScrolling(false);
   7891             if (applyTransformation) {
   7892                 setTransformationMethod(null);
   7893             }
   7894         }
   7895     }
   7896 
   7897     /**
   7898      * Causes words in the text that are longer than the view's width
   7899      * to be ellipsized instead of broken in the middle.  You may also
   7900      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
   7901      * to constrain the text to a single line.  Use <code>null</code>
   7902      * to turn off ellipsizing.
   7903      *
   7904      * If {@link #setMaxLines} has been used to set two or more lines,
   7905      * only {@link android.text.TextUtils.TruncateAt#END} and
   7906      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
   7907      * (other ellipsizing types will not do anything).
   7908      *
   7909      * @attr ref android.R.styleable#TextView_ellipsize
   7910      */
   7911     public void setEllipsize(TextUtils.TruncateAt where) {
   7912         // TruncateAt is an enum. != comparison is ok between these singleton objects.
   7913         if (mEllipsize != where) {
   7914             mEllipsize = where;
   7915 
   7916             if (mLayout != null) {
   7917                 nullLayouts();
   7918                 requestLayout();
   7919                 invalidate();
   7920             }
   7921         }
   7922     }
   7923 
   7924     /**
   7925      * Sets how many times to repeat the marquee animation. Only applied if the
   7926      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
   7927      *
   7928      * @see #getMarqueeRepeatLimit()
   7929      *
   7930      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
   7931      */
   7932     public void setMarqueeRepeatLimit(int marqueeLimit) {
   7933         mMarqueeRepeatLimit = marqueeLimit;
   7934     }
   7935 
   7936     /**
   7937      * Gets the number of times the marquee animation is repeated. Only meaningful if the
   7938      * TextView has marquee enabled.
   7939      *
   7940      * @return the number of times the marquee animation is repeated. -1 if the animation
   7941      * repeats indefinitely
   7942      *
   7943      * @see #setMarqueeRepeatLimit(int)
   7944      *
   7945      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
   7946      */
   7947     public int getMarqueeRepeatLimit() {
   7948         return mMarqueeRepeatLimit;
   7949     }
   7950 
   7951     /**
   7952      * Returns where, if anywhere, words that are longer than the view
   7953      * is wide should be ellipsized.
   7954      */
   7955     @ViewDebug.ExportedProperty
   7956     public TextUtils.TruncateAt getEllipsize() {
   7957         return mEllipsize;
   7958     }
   7959 
   7960     /**
   7961      * Set the TextView so that when it takes focus, all the text is
   7962      * selected.
   7963      *
   7964      * @attr ref android.R.styleable#TextView_selectAllOnFocus
   7965      */
   7966     @android.view.RemotableViewMethod
   7967     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
   7968         createEditorIfNeeded();
   7969         mEditor.mSelectAllOnFocus = selectAllOnFocus;
   7970 
   7971         if (selectAllOnFocus && !(mText instanceof Spannable)) {
   7972             setText(mText, BufferType.SPANNABLE);
   7973         }
   7974     }
   7975 
   7976     /**
   7977      * Set whether the cursor is visible. The default is true. Note that this property only
   7978      * makes sense for editable TextView.
   7979      *
   7980      * @see #isCursorVisible()
   7981      *
   7982      * @attr ref android.R.styleable#TextView_cursorVisible
   7983      */
   7984     @android.view.RemotableViewMethod
   7985     public void setCursorVisible(boolean visible) {
   7986         if (visible && mEditor == null) return; // visible is the default value with no edit data
   7987         createEditorIfNeeded();
   7988         if (mEditor.mCursorVisible != visible) {
   7989             mEditor.mCursorVisible = visible;
   7990             invalidate();
   7991 
   7992             mEditor.makeBlink();
   7993 
   7994             // InsertionPointCursorController depends on mCursorVisible
   7995             mEditor.prepareCursorControllers();
   7996         }
   7997     }
   7998 
   7999     /**
   8000      * @return whether or not the cursor is visible (assuming this TextView is editable)
   8001      *
   8002      * @see #setCursorVisible(boolean)
   8003      *
   8004      * @attr ref android.R.styleable#TextView_cursorVisible
   8005      */
   8006     public boolean isCursorVisible() {
   8007         // true is the default value
   8008         return mEditor == null ? true : mEditor.mCursorVisible;
   8009     }
   8010 
   8011     private boolean canMarquee() {
   8012         int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
   8013         return width > 0 && (mLayout.getLineWidth(0) > width ||
   8014                 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
   8015                         mSavedMarqueeModeLayout.getLineWidth(0) > width));
   8016     }
   8017 
   8018     private void startMarquee() {
   8019         // Do not ellipsize EditText
   8020         if (getKeyListener() != null) return;
   8021 
   8022         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
   8023             return;
   8024         }
   8025 
   8026         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
   8027                 getLineCount() == 1 && canMarquee()) {
   8028 
   8029             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   8030                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
   8031                 final Layout tmp = mLayout;
   8032                 mLayout = mSavedMarqueeModeLayout;
   8033                 mSavedMarqueeModeLayout = tmp;
   8034                 setHorizontalFadingEdgeEnabled(true);
   8035                 requestLayout();
   8036                 invalidate();
   8037             }
   8038 
   8039             if (mMarquee == null) mMarquee = new Marquee(this);
   8040             mMarquee.start(mMarqueeRepeatLimit);
   8041         }
   8042     }
   8043 
   8044     private void stopMarquee() {
   8045         if (mMarquee != null && !mMarquee.isStopped()) {
   8046             mMarquee.stop();
   8047         }
   8048 
   8049         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
   8050             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   8051             final Layout tmp = mSavedMarqueeModeLayout;
   8052             mSavedMarqueeModeLayout = mLayout;
   8053             mLayout = tmp;
   8054             setHorizontalFadingEdgeEnabled(false);
   8055             requestLayout();
   8056             invalidate();
   8057         }
   8058     }
   8059 
   8060     private void startStopMarquee(boolean start) {
   8061         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   8062             if (start) {
   8063                 startMarquee();
   8064             } else {
   8065                 stopMarquee();
   8066             }
   8067         }
   8068     }
   8069 
   8070     /**
   8071      * This method is called when the text is changed, in case any subclasses
   8072      * would like to know.
   8073      *
   8074      * Within <code>text</code>, the <code>lengthAfter</code> characters
   8075      * beginning at <code>start</code> have just replaced old text that had
   8076      * length <code>lengthBefore</code>. It is an error to attempt to make
   8077      * changes to <code>text</code> from this callback.
   8078      *
   8079      * @param text The text the TextView is displaying
   8080      * @param start The offset of the start of the range of the text that was
   8081      * modified
   8082      * @param lengthBefore The length of the former text that has been replaced
   8083      * @param lengthAfter The length of the replacement modified text
   8084      */
   8085     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
   8086         // intentionally empty, template pattern method can be overridden by subclasses
   8087     }
   8088 
   8089     /**
   8090      * This method is called when the selection has changed, in case any
   8091      * subclasses would like to know.
   8092      *
   8093      * @param selStart The new selection start location.
   8094      * @param selEnd The new selection end location.
   8095      */
   8096     protected void onSelectionChanged(int selStart, int selEnd) {
   8097         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
   8098     }
   8099 
   8100     /**
   8101      * Adds a TextWatcher to the list of those whose methods are called
   8102      * whenever this TextView's text changes.
   8103      * <p>
   8104      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
   8105      * not called after {@link #setText} calls.  Now, doing {@link #setText}
   8106      * if there are any text changed listeners forces the buffer type to
   8107      * Editable if it would not otherwise be and does call this method.
   8108      */
   8109     public void addTextChangedListener(TextWatcher watcher) {
   8110         if (mListeners == null) {
   8111             mListeners = new ArrayList<TextWatcher>();
   8112         }
   8113 
   8114         mListeners.add(watcher);
   8115     }
   8116 
   8117     /**
   8118      * Removes the specified TextWatcher from the list of those whose
   8119      * methods are called
   8120      * whenever this TextView's text changes.
   8121      */
   8122     public void removeTextChangedListener(TextWatcher watcher) {
   8123         if (mListeners != null) {
   8124             int i = mListeners.indexOf(watcher);
   8125 
   8126             if (i >= 0) {
   8127                 mListeners.remove(i);
   8128             }
   8129         }
   8130     }
   8131 
   8132     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
   8133         if (mListeners != null) {
   8134             final ArrayList<TextWatcher> list = mListeners;
   8135             final int count = list.size();
   8136             for (int i = 0; i < count; i++) {
   8137                 list.get(i).beforeTextChanged(text, start, before, after);
   8138             }
   8139         }
   8140 
   8141         // The spans that are inside or intersect the modified region no longer make sense
   8142         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
   8143         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
   8144     }
   8145 
   8146     // Removes all spans that are inside or actually overlap the start..end range
   8147     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
   8148         if (!(mText instanceof Editable)) return;
   8149         Editable text = (Editable) mText;
   8150 
   8151         T[] spans = text.getSpans(start, end, type);
   8152         final int length = spans.length;
   8153         for (int i = 0; i < length; i++) {
   8154             final int spanStart = text.getSpanStart(spans[i]);
   8155             final int spanEnd = text.getSpanEnd(spans[i]);
   8156             if (spanEnd == start || spanStart == end) break;
   8157             text.removeSpan(spans[i]);
   8158         }
   8159     }
   8160 
   8161     void removeAdjacentSuggestionSpans(final int pos) {
   8162         if (!(mText instanceof Editable)) return;
   8163         final Editable text = (Editable) mText;
   8164 
   8165         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
   8166         final int length = spans.length;
   8167         for (int i = 0; i < length; i++) {
   8168             final int spanStart = text.getSpanStart(spans[i]);
   8169             final int spanEnd = text.getSpanEnd(spans[i]);
   8170             if (spanEnd == pos || spanStart == pos) {
   8171                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
   8172                     text.removeSpan(spans[i]);
   8173                 }
   8174             }
   8175         }
   8176     }
   8177 
   8178     /**
   8179      * Not private so it can be called from an inner class without going
   8180      * through a thunk.
   8181      */
   8182     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
   8183         if (mListeners != null) {
   8184             final ArrayList<TextWatcher> list = mListeners;
   8185             final int count = list.size();
   8186             for (int i = 0; i < count; i++) {
   8187                 list.get(i).onTextChanged(text, start, before, after);
   8188             }
   8189         }
   8190 
   8191         if (mEditor != null) mEditor.sendOnTextChanged(start, after);
   8192     }
   8193 
   8194     /**
   8195      * Not private so it can be called from an inner class without going
   8196      * through a thunk.
   8197      */
   8198     void sendAfterTextChanged(Editable text) {
   8199         if (mListeners != null) {
   8200             final ArrayList<TextWatcher> list = mListeners;
   8201             final int count = list.size();
   8202             for (int i = 0; i < count; i++) {
   8203                 list.get(i).afterTextChanged(text);
   8204             }
   8205         }
   8206         hideErrorIfUnchanged();
   8207     }
   8208 
   8209     void updateAfterEdit() {
   8210         invalidate();
   8211         int curs = getSelectionStart();
   8212 
   8213         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   8214             registerForPreDraw();
   8215         }
   8216 
   8217         checkForResize();
   8218 
   8219         if (curs >= 0) {
   8220             mHighlightPathBogus = true;
   8221             if (mEditor != null) mEditor.makeBlink();
   8222             bringPointIntoView(curs);
   8223         }
   8224     }
   8225 
   8226     /**
   8227      * Not private so it can be called from an inner class without going
   8228      * through a thunk.
   8229      */
   8230     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
   8231         sLastCutCopyOrTextChangedTime = 0;
   8232 
   8233         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
   8234         if (ims == null || ims.mBatchEditNesting == 0) {
   8235             updateAfterEdit();
   8236         }
   8237         if (ims != null) {
   8238             ims.mContentChanged = true;
   8239             if (ims.mChangedStart < 0) {
   8240                 ims.mChangedStart = start;
   8241                 ims.mChangedEnd = start+before;
   8242             } else {
   8243                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
   8244                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
   8245             }
   8246             ims.mChangedDelta += after-before;
   8247         }
   8248         resetErrorChangedFlag();
   8249         sendOnTextChanged(buffer, start, before, after);
   8250         onTextChanged(buffer, start, before, after);
   8251     }
   8252 
   8253     /**
   8254      * Not private so it can be called from an inner class without going
   8255      * through a thunk.
   8256      */
   8257     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
   8258         // XXX Make the start and end move together if this ends up
   8259         // spending too much time invalidating.
   8260 
   8261         boolean selChanged = false;
   8262         int newSelStart=-1, newSelEnd=-1;
   8263 
   8264         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
   8265 
   8266         if (what == Selection.SELECTION_END) {
   8267             selChanged = true;
   8268             newSelEnd = newStart;
   8269 
   8270             if (oldStart >= 0 || newStart >= 0) {
   8271                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
   8272                 checkForResize();
   8273                 registerForPreDraw();
   8274                 if (mEditor != null) mEditor.makeBlink();
   8275             }
   8276         }
   8277 
   8278         if (what == Selection.SELECTION_START) {
   8279             selChanged = true;
   8280             newSelStart = newStart;
   8281 
   8282             if (oldStart >= 0 || newStart >= 0) {
   8283                 int end = Selection.getSelectionEnd(buf);
   8284                 invalidateCursor(end, oldStart, newStart);
   8285             }
   8286         }
   8287 
   8288         if (selChanged) {
   8289             mHighlightPathBogus = true;
   8290             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
   8291 
   8292             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
   8293                 if (newSelStart < 0) {
   8294                     newSelStart = Selection.getSelectionStart(buf);
   8295                 }
   8296                 if (newSelEnd < 0) {
   8297                     newSelEnd = Selection.getSelectionEnd(buf);
   8298                 }
   8299 
   8300                 if (mEditor != null) {
   8301                     mEditor.refreshTextActionMode();
   8302                     if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) {
   8303                         // User generated selection has been removed.
   8304                         setHasTransientState(false);
   8305                     }
   8306                 }
   8307                 onSelectionChanged(newSelStart, newSelEnd);
   8308             }
   8309         }
   8310 
   8311         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
   8312                 what instanceof CharacterStyle) {
   8313             if (ims == null || ims.mBatchEditNesting == 0) {
   8314                 invalidate();
   8315                 mHighlightPathBogus = true;
   8316                 checkForResize();
   8317             } else {
   8318                 ims.mContentChanged = true;
   8319             }
   8320             if (mEditor != null) {
   8321                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
   8322                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
   8323                 mEditor.invalidateHandlesAndActionMode();
   8324             }
   8325         }
   8326 
   8327         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
   8328             mHighlightPathBogus = true;
   8329             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
   8330                 ims.mSelectionModeChanged = true;
   8331             }
   8332 
   8333             if (Selection.getSelectionStart(buf) >= 0) {
   8334                 if (ims == null || ims.mBatchEditNesting == 0) {
   8335                     invalidateCursor();
   8336                 } else {
   8337                     ims.mCursorChanged = true;
   8338                 }
   8339             }
   8340         }
   8341 
   8342         if (what instanceof ParcelableSpan) {
   8343             // If this is a span that can be sent to a remote process,
   8344             // the current extract editor would be interested in it.
   8345             if (ims != null && ims.mExtractedTextRequest != null) {
   8346                 if (ims.mBatchEditNesting != 0) {
   8347                     if (oldStart >= 0) {
   8348                         if (ims.mChangedStart > oldStart) {
   8349                             ims.mChangedStart = oldStart;
   8350                         }
   8351                         if (ims.mChangedStart > oldEnd) {
   8352                             ims.mChangedStart = oldEnd;
   8353                         }
   8354                     }
   8355                     if (newStart >= 0) {
   8356                         if (ims.mChangedStart > newStart) {
   8357                             ims.mChangedStart = newStart;
   8358                         }
   8359                         if (ims.mChangedStart > newEnd) {
   8360                             ims.mChangedStart = newEnd;
   8361                         }
   8362                     }
   8363                 } else {
   8364                     if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
   8365                             + oldStart + "-" + oldEnd + ","
   8366                             + newStart + "-" + newEnd + " " + what);
   8367                     ims.mContentChanged = true;
   8368                 }
   8369             }
   8370         }
   8371 
   8372         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
   8373                 what instanceof SpellCheckSpan) {
   8374             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
   8375         }
   8376     }
   8377 
   8378     @Override
   8379     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
   8380         if (isTemporarilyDetached()) {
   8381             // If we are temporarily in the detach state, then do nothing.
   8382             super.onFocusChanged(focused, direction, previouslyFocusedRect);
   8383             return;
   8384         }
   8385 
   8386         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
   8387 
   8388         if (focused) {
   8389             if (mText instanceof Spannable) {
   8390                 Spannable sp = (Spannable) mText;
   8391                 MetaKeyKeyListener.resetMetaState(sp);
   8392             }
   8393         }
   8394 
   8395         startStopMarquee(focused);
   8396 
   8397         if (mTransformation != null) {
   8398             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
   8399         }
   8400 
   8401         super.onFocusChanged(focused, direction, previouslyFocusedRect);
   8402     }
   8403 
   8404     @Override
   8405     public void onWindowFocusChanged(boolean hasWindowFocus) {
   8406         super.onWindowFocusChanged(hasWindowFocus);
   8407 
   8408         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
   8409 
   8410         startStopMarquee(hasWindowFocus);
   8411     }
   8412 
   8413     @Override
   8414     protected void onVisibilityChanged(View changedView, int visibility) {
   8415         super.onVisibilityChanged(changedView, visibility);
   8416         if (mEditor != null && visibility != VISIBLE) {
   8417             mEditor.hideCursorAndSpanControllers();
   8418             stopTextActionMode();
   8419         }
   8420     }
   8421 
   8422     /**
   8423      * Use {@link BaseInputConnection#removeComposingSpans
   8424      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
   8425      * state from this text view.
   8426      */
   8427     public void clearComposingText() {
   8428         if (mText instanceof Spannable) {
   8429             BaseInputConnection.removeComposingSpans((Spannable)mText);
   8430         }
   8431     }
   8432 
   8433     @Override
   8434     public void setSelected(boolean selected) {
   8435         boolean wasSelected = isSelected();
   8436 
   8437         super.setSelected(selected);
   8438 
   8439         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   8440             if (selected) {
   8441                 startMarquee();
   8442             } else {
   8443                 stopMarquee();
   8444             }
   8445         }
   8446     }
   8447 
   8448     @Override
   8449     public boolean onTouchEvent(MotionEvent event) {
   8450         final int action = event.getActionMasked();
   8451         if (mEditor != null) {
   8452             mEditor.onTouchEvent(event);
   8453 
   8454             if (mEditor.mSelectionModifierCursorController != null &&
   8455                     mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
   8456                 return true;
   8457             }
   8458         }
   8459 
   8460         final boolean superResult = super.onTouchEvent(event);
   8461 
   8462         /*
   8463          * Don't handle the release after a long press, because it will move the selection away from
   8464          * whatever the menu action was trying to affect. If the long press should have triggered an
   8465          * insertion action mode, we can now actually show it.
   8466          */
   8467         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
   8468             mEditor.mDiscardNextActionUp = false;
   8469 
   8470             if (mEditor.mIsInsertionActionModeStartPending) {
   8471                 mEditor.startInsertionActionMode();
   8472                 mEditor.mIsInsertionActionModeStartPending = false;
   8473             }
   8474             return superResult;
   8475         }
   8476 
   8477         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
   8478                 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
   8479 
   8480          if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
   8481                 && mText instanceof Spannable && mLayout != null) {
   8482             boolean handled = false;
   8483 
   8484             if (mMovement != null) {
   8485                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
   8486             }
   8487 
   8488             final boolean textIsSelectable = isTextSelectable();
   8489             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
   8490                 // The LinkMovementMethod which should handle taps on links has not been installed
   8491                 // on non editable text that support text selection.
   8492                 // We reproduce its behavior here to open links for these.
   8493                 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
   8494                         getSelectionEnd(), ClickableSpan.class);
   8495 
   8496                 if (links.length > 0) {
   8497                     links[0].onClick(this);
   8498                     handled = true;
   8499                 }
   8500             }
   8501 
   8502             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
   8503                 // Show the IME, except when selecting in read-only text.
   8504                 final InputMethodManager imm = InputMethodManager.peekInstance();
   8505                 viewClicked(imm);
   8506                 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
   8507                     handled |= imm != null && imm.showSoftInput(this, 0);
   8508                 }
   8509 
   8510                 // The above condition ensures that the mEditor is not null
   8511                 mEditor.onTouchUpEvent(event);
   8512 
   8513                 handled = true;
   8514             }
   8515 
   8516             if (handled) {
   8517                 return true;
   8518             }
   8519         }
   8520 
   8521         return superResult;
   8522     }
   8523 
   8524     @Override
   8525     public boolean onGenericMotionEvent(MotionEvent event) {
   8526         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
   8527             try {
   8528                 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
   8529                     return true;
   8530                 }
   8531             } catch (AbstractMethodError ex) {
   8532                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
   8533                 // Ignore its absence in case third party applications implemented the
   8534                 // interface directly.
   8535             }
   8536         }
   8537         return super.onGenericMotionEvent(event);
   8538     }
   8539 
   8540     @Override
   8541     protected void onCreateContextMenu(ContextMenu menu) {
   8542         if (mEditor != null) {
   8543             mEditor.onCreateContextMenu(menu);
   8544         }
   8545     }
   8546 
   8547     @Override
   8548     public boolean showContextMenu() {
   8549         if (mEditor != null) {
   8550             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
   8551         }
   8552         return super.showContextMenu();
   8553     }
   8554 
   8555     @Override
   8556     public boolean showContextMenu(float x, float y) {
   8557         if (mEditor != null) {
   8558             mEditor.setContextMenuAnchor(x, y);
   8559         }
   8560         return super.showContextMenu(x, y);
   8561     }
   8562 
   8563     /**
   8564      * @return True iff this TextView contains a text that can be edited, or if this is
   8565      * a selectable TextView.
   8566      */
   8567     boolean isTextEditable() {
   8568         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
   8569     }
   8570 
   8571     /**
   8572      * Returns true, only while processing a touch gesture, if the initial
   8573      * touch down event caused focus to move to the text view and as a result
   8574      * its selection changed.  Only valid while processing the touch gesture
   8575      * of interest, in an editable text view.
   8576      */
   8577     public boolean didTouchFocusSelect() {
   8578         return mEditor != null && mEditor.mTouchFocusSelected;
   8579     }
   8580 
   8581     @Override
   8582     public void cancelLongPress() {
   8583         super.cancelLongPress();
   8584         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
   8585     }
   8586 
   8587     @Override
   8588     public boolean onTrackballEvent(MotionEvent event) {
   8589         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
   8590             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
   8591                 return true;
   8592             }
   8593         }
   8594 
   8595         return super.onTrackballEvent(event);
   8596     }
   8597 
   8598     public void setScroller(Scroller s) {
   8599         mScroller = s;
   8600     }
   8601 
   8602     @Override
   8603     protected float getLeftFadingEdgeStrength() {
   8604         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
   8605             final Marquee marquee = mMarquee;
   8606             if (marquee.shouldDrawLeftFade()) {
   8607                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
   8608             } else {
   8609                 return 0.0f;
   8610             }
   8611         } else if (getLineCount() == 1) {
   8612             final float lineLeft = getLayout().getLineLeft(0);
   8613             if(lineLeft > mScrollX) return 0.0f;
   8614             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
   8615         }
   8616         return super.getLeftFadingEdgeStrength();
   8617     }
   8618 
   8619     @Override
   8620     protected float getRightFadingEdgeStrength() {
   8621         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
   8622             final Marquee marquee = mMarquee;
   8623             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
   8624         } else if (getLineCount() == 1) {
   8625             final float rightEdge = mScrollX + (getWidth() - getCompoundPaddingLeft() -
   8626                     getCompoundPaddingRight());
   8627             final float lineRight = getLayout().getLineRight(0);
   8628             if(lineRight < rightEdge) return 0.0f;
   8629             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
   8630         }
   8631         return super.getRightFadingEdgeStrength();
   8632     }
   8633 
   8634     /**
   8635      * Calculates the fading edge strength as the ratio of the distance between two
   8636      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
   8637      * value for the distance calculation.
   8638      *
   8639      * @param position1 A horizontal position.
   8640      * @param position2 A horizontal position.
   8641      * @return Fading edge strength between [0.0f, 1.0f].
   8642      */
   8643     @FloatRange(from=0.0, to=1.0)
   8644     private final float getHorizontalFadingEdgeStrength(float position1, float position2) {
   8645         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
   8646         if(horizontalFadingEdgeLength == 0) return 0.0f;
   8647         final float diff = Math.abs(position1 - position2);
   8648         if(diff > horizontalFadingEdgeLength) return 1.0f;
   8649         return diff / horizontalFadingEdgeLength;
   8650     }
   8651 
   8652     private final boolean isMarqueeFadeEnabled() {
   8653         return mEllipsize == TextUtils.TruncateAt.MARQUEE &&
   8654                 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   8655     }
   8656 
   8657     @Override
   8658     protected int computeHorizontalScrollRange() {
   8659         if (mLayout != null) {
   8660             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
   8661                     (int) mLayout.getLineWidth(0) : mLayout.getWidth();
   8662         }
   8663 
   8664         return super.computeHorizontalScrollRange();
   8665     }
   8666 
   8667     @Override
   8668     protected int computeVerticalScrollRange() {
   8669         if (mLayout != null)
   8670             return mLayout.getHeight();
   8671 
   8672         return super.computeVerticalScrollRange();
   8673     }
   8674 
   8675     @Override
   8676     protected int computeVerticalScrollExtent() {
   8677         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
   8678     }
   8679 
   8680     @Override
   8681     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
   8682         super.findViewsWithText(outViews, searched, flags);
   8683         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
   8684                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
   8685             String searchedLowerCase = searched.toString().toLowerCase();
   8686             String textLowerCase = mText.toString().toLowerCase();
   8687             if (textLowerCase.contains(searchedLowerCase)) {
   8688                 outViews.add(this);
   8689             }
   8690         }
   8691     }
   8692 
   8693     public enum BufferType {
   8694         NORMAL, SPANNABLE, EDITABLE,
   8695     }
   8696 
   8697     /**
   8698      * Returns the TextView_textColor attribute from the TypedArray, if set, or
   8699      * the TextAppearance_textColor from the TextView_textAppearance attribute,
   8700      * if TextView_textColor was not set directly.
   8701      *
   8702      * @removed
   8703      */
   8704     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
   8705         if (attrs == null) {
   8706             // Preserve behavior prior to removal of this API.
   8707             throw new NullPointerException();
   8708         }
   8709 
   8710         // It's not safe to use this method from apps. The parameter 'attrs'
   8711         // must have been obtained using the TextView filter array which is not
   8712         // available to the SDK. As such, we grab a default TypedArray with the
   8713         // right filter instead here.
   8714         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
   8715         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
   8716         if (colors == null) {
   8717             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
   8718             if (ap != 0) {
   8719                 final TypedArray appearance = context.obtainStyledAttributes(
   8720                         ap, R.styleable.TextAppearance);
   8721                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
   8722                 appearance.recycle();
   8723             }
   8724         }
   8725         a.recycle();
   8726 
   8727         return colors;
   8728     }
   8729 
   8730     /**
   8731      * Returns the default color from the TextView_textColor attribute from the
   8732      * AttributeSet, if set, or the default color from the
   8733      * TextAppearance_textColor from the TextView_textAppearance attribute, if
   8734      * TextView_textColor was not set directly.
   8735      *
   8736      * @removed
   8737      */
   8738     public static int getTextColor(Context context, TypedArray attrs, int def) {
   8739         final ColorStateList colors = getTextColors(context, attrs);
   8740         if (colors == null) {
   8741             return def;
   8742         } else {
   8743             return colors.getDefaultColor();
   8744         }
   8745     }
   8746 
   8747     @Override
   8748     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
   8749         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
   8750             // Handle Ctrl-only shortcuts.
   8751             switch (keyCode) {
   8752             case KeyEvent.KEYCODE_A:
   8753                 if (canSelectText()) {
   8754                     return onTextContextMenuItem(ID_SELECT_ALL);
   8755                 }
   8756                 break;
   8757             case KeyEvent.KEYCODE_Z:
   8758                 if (canUndo()) {
   8759                     return onTextContextMenuItem(ID_UNDO);
   8760                 }
   8761                 break;
   8762             case KeyEvent.KEYCODE_X:
   8763                 if (canCut()) {
   8764                     return onTextContextMenuItem(ID_CUT);
   8765                 }
   8766                 break;
   8767             case KeyEvent.KEYCODE_C:
   8768                 if (canCopy()) {
   8769                     return onTextContextMenuItem(ID_COPY);
   8770                 }
   8771                 break;
   8772             case KeyEvent.KEYCODE_V:
   8773                 if (canPaste()) {
   8774                     return onTextContextMenuItem(ID_PASTE);
   8775                 }
   8776                 break;
   8777             }
   8778         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
   8779             // Handle Ctrl-Shift shortcuts.
   8780             switch (keyCode) {
   8781                 case KeyEvent.KEYCODE_Z:
   8782                     if (canRedo()) {
   8783                         return onTextContextMenuItem(ID_REDO);
   8784                     }
   8785                     break;
   8786                 case KeyEvent.KEYCODE_V:
   8787                     if (canPaste()) {
   8788                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
   8789                     }
   8790             }
   8791         }
   8792         return super.onKeyShortcut(keyCode, event);
   8793     }
   8794 
   8795     /**
   8796      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
   8797      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
   8798      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
   8799      * sufficient.
   8800      */
   8801     boolean canSelectText() {
   8802         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
   8803     }
   8804 
   8805     /**
   8806      * Test based on the <i>intrinsic</i> charateristics of the TextView.
   8807      * The text must be spannable and the movement method must allow for arbitary selection.
   8808      *
   8809      * See also {@link #canSelectText()}.
   8810      */
   8811     boolean textCanBeSelected() {
   8812         // prepareCursorController() relies on this method.
   8813         // If you change this condition, make sure prepareCursorController is called anywhere
   8814         // the value of this condition might be changed.
   8815         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
   8816         return isTextEditable() ||
   8817                 (isTextSelectable() && mText instanceof Spannable && isEnabled());
   8818     }
   8819 
   8820     private Locale getTextServicesLocale(boolean allowNullLocale) {
   8821         // Start fetching the text services locale asynchronously.
   8822         updateTextServicesLocaleAsync();
   8823         // If !allowNullLocale and there is no cached text services locale, just return the default
   8824         // locale.
   8825         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
   8826                 : mCurrentSpellCheckerLocaleCache;
   8827     }
   8828 
   8829     /**
   8830      * This is a temporary method. Future versions may support multi-locale text.
   8831      * Caveat: This method may not return the latest text services locale, but this should be
   8832      * acceptable and it's more important to make this method asynchronous.
   8833      *
   8834      * @return The locale that should be used for a word iterator
   8835      * in this TextView, based on the current spell checker settings,
   8836      * the current IME's locale, or the system default locale.
   8837      * Please note that a word iterator in this TextView is different from another word iterator
   8838      * used by SpellChecker.java of TextView. This method should be used for the former.
   8839      * @hide
   8840      */
   8841     // TODO: Support multi-locale
   8842     // TODO: Update the text services locale immediately after the keyboard locale is switched
   8843     // by catching intent of keyboard switch event
   8844     public Locale getTextServicesLocale() {
   8845         return getTextServicesLocale(false /* allowNullLocale */);
   8846     }
   8847 
   8848     /**
   8849      * @return true if this TextView is specialized for showing and interacting with the extracted
   8850      * text in a full-screen input method.
   8851      * @hide
   8852      */
   8853     public boolean isInExtractedMode() {
   8854         return false;
   8855     }
   8856 
   8857     /**
   8858      * This is a temporary method. Future versions may support multi-locale text.
   8859      * Caveat: This method may not return the latest spell checker locale, but this should be
   8860      * acceptable and it's more important to make this method asynchronous.
   8861      *
   8862      * @return The locale that should be used for a spell checker in this TextView,
   8863      * based on the current spell checker settings, the current IME's locale, or the system default
   8864      * locale.
   8865      * @hide
   8866      */
   8867     public Locale getSpellCheckerLocale() {
   8868         return getTextServicesLocale(true /* allowNullLocale */);
   8869     }
   8870 
   8871     private void updateTextServicesLocaleAsync() {
   8872         // AsyncTask.execute() uses a serial executor which means we don't have
   8873         // to lock around updateTextServicesLocaleLocked() to prevent it from
   8874         // being executed n times in parallel.
   8875         AsyncTask.execute(new Runnable() {
   8876             @Override
   8877             public void run() {
   8878                 updateTextServicesLocaleLocked();
   8879             }
   8880         });
   8881     }
   8882 
   8883     private void updateTextServicesLocaleLocked() {
   8884         final TextServicesManager textServicesManager = (TextServicesManager)
   8885                 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
   8886         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
   8887         final Locale locale;
   8888         if (subtype != null) {
   8889             locale = subtype.getLocaleObject();
   8890         } else {
   8891             locale = null;
   8892         }
   8893         mCurrentSpellCheckerLocaleCache = locale;
   8894     }
   8895 
   8896     void onLocaleChanged() {
   8897         mEditor.onLocaleChanged();
   8898     }
   8899 
   8900     /**
   8901      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
   8902      * Made available to achieve a consistent behavior.
   8903      * @hide
   8904      */
   8905     public WordIterator getWordIterator() {
   8906         if (mEditor != null) {
   8907             return mEditor.getWordIterator();
   8908         } else {
   8909             return null;
   8910         }
   8911     }
   8912 
   8913     /** @hide */
   8914     @Override
   8915     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
   8916         super.onPopulateAccessibilityEventInternal(event);
   8917 
   8918         final CharSequence text = getTextForAccessibility();
   8919         if (!TextUtils.isEmpty(text)) {
   8920             event.getText().add(text);
   8921         }
   8922     }
   8923 
   8924     /**
   8925      * @return true if the user has explicitly allowed accessibility services
   8926      * to speak passwords.
   8927      */
   8928     private boolean shouldSpeakPasswordsForAccessibility() {
   8929         return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
   8930                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
   8931                 UserHandle.USER_CURRENT_OR_SELF) == 1);
   8932     }
   8933 
   8934     @Override
   8935     public CharSequence getAccessibilityClassName() {
   8936         return TextView.class.getName();
   8937     }
   8938 
   8939     @Override
   8940     public void onProvideStructure(ViewStructure structure) {
   8941         super.onProvideStructure(structure);
   8942         final boolean isPassword = hasPasswordTransformationMethod()
   8943                 || isPasswordInputType(getInputType());
   8944         if (!isPassword) {
   8945             if (mLayout == null) {
   8946                 assumeLayout();
   8947             }
   8948             Layout layout = mLayout;
   8949             final int lineCount = layout.getLineCount();
   8950             if (lineCount <= 1) {
   8951                 // Simple case: this is a single line.
   8952                 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
   8953             } else {
   8954                 // Complex case: multi-line, could be scrolled or within a scroll container
   8955                 // so some lines are not visible.
   8956                 final int[] tmpCords = new int[2];
   8957                 getLocationInWindow(tmpCords);
   8958                 final int topWindowLocation = tmpCords[1];
   8959                 View root = this;
   8960                 ViewParent viewParent = getParent();
   8961                 while (viewParent instanceof View) {
   8962                     root = (View) viewParent;
   8963                     viewParent = root.getParent();
   8964                 }
   8965                 final int windowHeight = root.getHeight();
   8966                 final int topLine;
   8967                 final int bottomLine;
   8968                 if (topWindowLocation >= 0) {
   8969                     // The top of the view is fully within its window; start text at line 0.
   8970                     topLine = getLineAtCoordinateUnclamped(0);
   8971                     bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
   8972                 } else {
   8973                     // The top of hte window has scrolled off the top of the window; figure out
   8974                     // the starting line for this.
   8975                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
   8976                     bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
   8977                 }
   8978                 // We want to return some contextual lines above/below the lines that are
   8979                 // actually visible.
   8980                 int expandedTopLine = topLine - (bottomLine-topLine)/2;
   8981                 if (expandedTopLine < 0) {
   8982                     expandedTopLine = 0;
   8983                 }
   8984                 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
   8985                 if (expandedBottomLine >= lineCount) {
   8986                     expandedBottomLine = lineCount-1;
   8987                 }
   8988                 // Convert lines into character offsets.
   8989                 int expandedTopChar = layout.getLineStart(expandedTopLine);
   8990                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
   8991                 // Take into account selection -- if there is a selection, we need to expand
   8992                 // the text we are returning to include that selection.
   8993                 final int selStart = getSelectionStart();
   8994                 final int selEnd = getSelectionEnd();
   8995                 if (selStart < selEnd) {
   8996                     if (selStart < expandedTopChar) {
   8997                         expandedTopChar = selStart;
   8998                     }
   8999                     if (selEnd > expandedBottomChar) {
   9000                         expandedBottomChar = selEnd;
   9001                     }
   9002                 }
   9003                 // Get the text and trim it to the range we are reporting.
   9004                 CharSequence text = getText();
   9005                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
   9006                     text = text.subSequence(expandedTopChar, expandedBottomChar);
   9007                 }
   9008                 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
   9009                 final int[] lineOffsets = new int[bottomLine-topLine+1];
   9010                 final int[] lineBaselines = new int[bottomLine-topLine+1];
   9011                 final int baselineOffset = getBaselineOffset();
   9012                 for (int i=topLine; i<=bottomLine; i++) {
   9013                     lineOffsets[i-topLine] = layout.getLineStart(i);
   9014                     lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
   9015                 }
   9016                 structure.setTextLines(lineOffsets, lineBaselines);
   9017             }
   9018 
   9019             // Extract style information that applies to the TextView as a whole.
   9020             int style = 0;
   9021             int typefaceStyle = getTypefaceStyle();
   9022             if ((typefaceStyle & Typeface.BOLD) != 0) {
   9023                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
   9024             }
   9025             if ((typefaceStyle & Typeface.ITALIC) != 0) {
   9026                 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
   9027             }
   9028 
   9029             // Global styles can also be set via TextView.setPaintFlags().
   9030             int paintFlags = mTextPaint.getFlags();
   9031             if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
   9032                 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
   9033             }
   9034             if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
   9035                 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
   9036             }
   9037             if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
   9038                 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
   9039             }
   9040 
   9041             // TextView does not have its own text background color. A background is either part
   9042             // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
   9043             structure.setTextStyle(getTextSize(), getCurrentTextColor(),
   9044                     AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
   9045         }
   9046         structure.setHint(getHint());
   9047     }
   9048 
   9049     /** @hide */
   9050     @Override
   9051     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
   9052         super.onInitializeAccessibilityEventInternal(event);
   9053 
   9054         final boolean isPassword = hasPasswordTransformationMethod();
   9055         event.setPassword(isPassword);
   9056 
   9057         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
   9058             event.setFromIndex(Selection.getSelectionStart(mText));
   9059             event.setToIndex(Selection.getSelectionEnd(mText));
   9060             event.setItemCount(mText.length());
   9061         }
   9062     }
   9063 
   9064     /** @hide */
   9065     @Override
   9066     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   9067         super.onInitializeAccessibilityNodeInfoInternal(info);
   9068 
   9069         final boolean isPassword = hasPasswordTransformationMethod();
   9070         info.setPassword(isPassword);
   9071         info.setText(getTextForAccessibility());
   9072 
   9073         if (mBufferType == BufferType.EDITABLE) {
   9074             info.setEditable(true);
   9075             if (isEnabled()) {
   9076                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
   9077             }
   9078         }
   9079 
   9080         if (mEditor != null) {
   9081             info.setInputType(mEditor.mInputType);
   9082 
   9083             if (mEditor.mError != null) {
   9084                 info.setContentInvalid(true);
   9085                 info.setError(mEditor.mError);
   9086             }
   9087         }
   9088 
   9089         if (!TextUtils.isEmpty(mText)) {
   9090             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
   9091             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
   9092             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
   9093                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
   9094                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
   9095                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
   9096                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
   9097             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
   9098         }
   9099 
   9100         if (isFocused()) {
   9101             if (canCopy()) {
   9102                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
   9103             }
   9104             if (canPaste()) {
   9105                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
   9106             }
   9107             if (canCut()) {
   9108                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
   9109             }
   9110             if (canShare()) {
   9111                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
   9112                         ACCESSIBILITY_ACTION_SHARE,
   9113                         getResources().getString(com.android.internal.R.string.share)));
   9114             }
   9115             if (canProcessText()) {  // also implies mEditor is not null.
   9116                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
   9117             }
   9118         }
   9119 
   9120         // Check for known input filter types.
   9121         final int numFilters = mFilters.length;
   9122         for (int i = 0; i < numFilters; i++) {
   9123             final InputFilter filter = mFilters[i];
   9124             if (filter instanceof InputFilter.LengthFilter) {
   9125                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
   9126             }
   9127         }
   9128 
   9129         if (!isSingleLine()) {
   9130             info.setMultiLine(true);
   9131         }
   9132     }
   9133 
   9134     /**
   9135      * Performs an accessibility action after it has been offered to the
   9136      * delegate.
   9137      *
   9138      * @hide
   9139      */
   9140     @Override
   9141     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   9142         if (mEditor != null
   9143                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
   9144             return true;
   9145         }
   9146         switch (action) {
   9147             case AccessibilityNodeInfo.ACTION_CLICK: {
   9148                 return performAccessibilityActionClick(arguments);
   9149             }
   9150             case AccessibilityNodeInfo.ACTION_COPY: {
   9151                 if (isFocused() && canCopy()) {
   9152                     if (onTextContextMenuItem(ID_COPY)) {
   9153                         return true;
   9154                     }
   9155                 }
   9156             } return false;
   9157             case AccessibilityNodeInfo.ACTION_PASTE: {
   9158                 if (isFocused() && canPaste()) {
   9159                     if (onTextContextMenuItem(ID_PASTE)) {
   9160                         return true;
   9161                     }
   9162                 }
   9163             } return false;
   9164             case AccessibilityNodeInfo.ACTION_CUT: {
   9165                 if (isFocused() && canCut()) {
   9166                     if (onTextContextMenuItem(ID_CUT)) {
   9167                         return true;
   9168                     }
   9169                 }
   9170             } return false;
   9171             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
   9172                 ensureIterableTextForAccessibilitySelectable();
   9173                 CharSequence text = getIterableTextForAccessibility();
   9174                 if (text == null) {
   9175                     return false;
   9176                 }
   9177                 final int start = (arguments != null) ? arguments.getInt(
   9178                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
   9179                 final int end = (arguments != null) ? arguments.getInt(
   9180                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
   9181                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
   9182                     // No arguments clears the selection.
   9183                     if (start == end && end == -1) {
   9184                         Selection.removeSelection((Spannable) text);
   9185                         return true;
   9186                     }
   9187                     if (start >= 0 && start <= end && end <= text.length()) {
   9188                         Selection.setSelection((Spannable) text, start, end);
   9189                         // Make sure selection mode is engaged.
   9190                         if (mEditor != null) {
   9191                             mEditor.startSelectionActionMode();
   9192                         }
   9193                         return true;
   9194                     }
   9195                 }
   9196             } return false;
   9197             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
   9198             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
   9199                 ensureIterableTextForAccessibilitySelectable();
   9200                 return super.performAccessibilityActionInternal(action, arguments);
   9201             }
   9202             case ACCESSIBILITY_ACTION_SHARE: {
   9203                 if (isFocused() && canShare()) {
   9204                     if (onTextContextMenuItem(ID_SHARE)) {
   9205                         return true;
   9206                     }
   9207                 }
   9208             } return false;
   9209             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
   9210                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
   9211                     return false;
   9212                 }
   9213                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
   9214                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
   9215                 setText(text);
   9216                 if (mText != null) {
   9217                     int updatedTextLength = mText.length();
   9218                     if (updatedTextLength > 0) {
   9219                         Selection.setSelection((Spannable) mText, updatedTextLength);
   9220                     }
   9221                 }
   9222             } return true;
   9223             default: {
   9224                 return super.performAccessibilityActionInternal(action, arguments);
   9225             }
   9226         }
   9227     }
   9228 
   9229     private boolean performAccessibilityActionClick(Bundle arguments) {
   9230         boolean handled = false;
   9231 
   9232         if (!isEnabled()) {
   9233             return false;
   9234         }
   9235 
   9236         if (isClickable() || isLongClickable()) {
   9237             // Simulate View.onTouchEvent for an ACTION_UP event
   9238             if (isFocusable() && !isFocused()) {
   9239                 requestFocus();
   9240             }
   9241 
   9242             performClick();
   9243             handled = true;
   9244         }
   9245 
   9246         // Show the IME, except when selecting in read-only text.
   9247         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
   9248                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
   9249             final InputMethodManager imm = InputMethodManager.peekInstance();
   9250             viewClicked(imm);
   9251             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
   9252                 handled |= imm.showSoftInput(this, 0);
   9253             }
   9254         }
   9255 
   9256         return handled;
   9257     }
   9258 
   9259     private boolean hasSpannableText() {
   9260         return mText != null && mText instanceof Spannable;
   9261     }
   9262 
   9263     /** @hide */
   9264     @Override
   9265     public void sendAccessibilityEventInternal(int eventType) {
   9266         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
   9267             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
   9268         }
   9269 
   9270         // Do not send scroll events since first they are not interesting for
   9271         // accessibility and second such events a generated too frequently.
   9272         // For details see the implementation of bringTextIntoView().
   9273         if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
   9274             return;
   9275         }
   9276         super.sendAccessibilityEventInternal(eventType);
   9277     }
   9278 
   9279     /**
   9280      * Returns the text that should be exposed to accessibility services.
   9281      * <p>
   9282      * This approximates what is displayed visually. If the user has specified
   9283      * that accessibility services should speak passwords, this method will
   9284      * bypass any password transformation method and return unobscured text.
   9285      *
   9286      * @return the text that should be exposed to accessibility services, may
   9287      *         be {@code null} if no text is set
   9288      */
   9289     @Nullable
   9290     private CharSequence getTextForAccessibility() {
   9291         // If the text is empty, we must be showing the hint text.
   9292         if (TextUtils.isEmpty(mText)) {
   9293             return mHint;
   9294         }
   9295 
   9296         // Check whether we need to bypass the transformation
   9297         // method and expose unobscured text.
   9298         if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
   9299             return mText;
   9300         }
   9301 
   9302         // Otherwise, speak whatever text is being displayed.
   9303         return mTransformed;
   9304     }
   9305 
   9306     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
   9307             int fromIndex, int removedCount, int addedCount) {
   9308         AccessibilityEvent event =
   9309                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
   9310         event.setFromIndex(fromIndex);
   9311         event.setRemovedCount(removedCount);
   9312         event.setAddedCount(addedCount);
   9313         event.setBeforeText(beforeText);
   9314         sendAccessibilityEventUnchecked(event);
   9315     }
   9316 
   9317     /**
   9318      * Returns whether this text view is a current input method target.  The
   9319      * default implementation just checks with {@link InputMethodManager}.
   9320      */
   9321     public boolean isInputMethodTarget() {
   9322         InputMethodManager imm = InputMethodManager.peekInstance();
   9323         return imm != null && imm.isActive(this);
   9324     }
   9325 
   9326     static final int ID_SELECT_ALL = android.R.id.selectAll;
   9327     static final int ID_UNDO = android.R.id.undo;
   9328     static final int ID_REDO = android.R.id.redo;
   9329     static final int ID_CUT = android.R.id.cut;
   9330     static final int ID_COPY = android.R.id.copy;
   9331     static final int ID_PASTE = android.R.id.paste;
   9332     static final int ID_SHARE = android.R.id.shareText;
   9333     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
   9334     static final int ID_REPLACE = android.R.id.replaceText;
   9335 
   9336     /**
   9337      * Called when a context menu option for the text view is selected.  Currently
   9338      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
   9339      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
   9340      *
   9341      * @return true if the context menu item action was performed.
   9342      */
   9343     public boolean onTextContextMenuItem(int id) {
   9344         int min = 0;
   9345         int max = mText.length();
   9346 
   9347         if (isFocused()) {
   9348             final int selStart = getSelectionStart();
   9349             final int selEnd = getSelectionEnd();
   9350 
   9351             min = Math.max(0, Math.min(selStart, selEnd));
   9352             max = Math.max(0, Math.max(selStart, selEnd));
   9353         }
   9354 
   9355         switch (id) {
   9356             case ID_SELECT_ALL:
   9357                 selectAllText();
   9358                 return true;
   9359 
   9360             case ID_UNDO:
   9361                 if (mEditor != null) {
   9362                     mEditor.undo();
   9363                 }
   9364                 return true;  // Returns true even if nothing was undone.
   9365 
   9366             case ID_REDO:
   9367                 if (mEditor != null) {
   9368                     mEditor.redo();
   9369                 }
   9370                 return true;  // Returns true even if nothing was undone.
   9371 
   9372             case ID_PASTE:
   9373                 paste(min, max, true /* withFormatting */);
   9374                 return true;
   9375 
   9376             case ID_PASTE_AS_PLAIN_TEXT:
   9377                 paste(min, max, false /* withFormatting */);
   9378                 return true;
   9379 
   9380             case ID_CUT:
   9381                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
   9382                 deleteText_internal(min, max);
   9383                 return true;
   9384 
   9385             case ID_COPY:
   9386                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
   9387                 stopTextActionMode();
   9388                 return true;
   9389 
   9390             case ID_REPLACE:
   9391                 if (mEditor != null) {
   9392                     mEditor.replace();
   9393                 }
   9394                 return true;
   9395 
   9396             case ID_SHARE:
   9397                 shareSelectedText();
   9398                 return true;
   9399         }
   9400         return false;
   9401     }
   9402 
   9403     CharSequence getTransformedText(int start, int end) {
   9404         return removeSuggestionSpans(mTransformed.subSequence(start, end));
   9405     }
   9406 
   9407     @Override
   9408     public boolean performLongClick() {
   9409         boolean handled = false;
   9410 
   9411         if (mEditor != null) {
   9412             mEditor.mIsBeingLongClicked = true;
   9413         }
   9414 
   9415         if (super.performLongClick()) {
   9416             handled = true;
   9417         }
   9418 
   9419         if (mEditor != null) {
   9420             handled |= mEditor.performLongClick(handled);
   9421             mEditor.mIsBeingLongClicked = false;
   9422         }
   9423 
   9424         if (handled) {
   9425             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   9426             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
   9427         }
   9428 
   9429         return handled;
   9430     }
   9431 
   9432     @Override
   9433     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
   9434         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
   9435         if (mEditor != null) {
   9436             mEditor.onScrollChanged();
   9437         }
   9438     }
   9439 
   9440     /**
   9441      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
   9442      * by the IME or by the spell checker as the user types. This is done by adding
   9443      * {@link SuggestionSpan}s to the text.
   9444      *
   9445      * When suggestions are enabled (default), this list of suggestions will be displayed when the
   9446      * user asks for them on these parts of the text. This value depends on the inputType of this
   9447      * TextView.
   9448      *
   9449      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
   9450      *
   9451      * In addition, the type variation must be one of
   9452      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
   9453      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
   9454      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
   9455      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
   9456      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
   9457      *
   9458      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
   9459      *
   9460      * @return true if the suggestions popup window is enabled, based on the inputType.
   9461      */
   9462     public boolean isSuggestionsEnabled() {
   9463         if (mEditor == null) return false;
   9464         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
   9465             return false;
   9466         }
   9467         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
   9468 
   9469         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   9470         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
   9471                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
   9472                 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
   9473                 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
   9474                 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
   9475     }
   9476 
   9477     /**
   9478      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
   9479      * selection is initiated in this View.
   9480      *
   9481      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
   9482      * Paste, Replace and Share actions, depending on what this View supports.
   9483      *
   9484      * <p>A custom implementation can add new entries in the default menu in its
   9485      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
   9486      * method. The default actions can also be removed from the menu using
   9487      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
   9488      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
   9489      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
   9490      *
   9491      * <p>Returning false from
   9492      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
   9493      * will prevent the action mode from being started.
   9494      *
   9495      * <p>Action click events should be handled by the custom implementation of
   9496      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
   9497      * android.view.MenuItem)}.
   9498      *
   9499      * <p>Note that text selection mode is not started when a TextView receives focus and the
   9500      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
   9501      * that case, to allow for quick replacement.
   9502      */
   9503     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
   9504         createEditorIfNeeded();
   9505         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
   9506     }
   9507 
   9508     /**
   9509      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
   9510      *
   9511      * @return The current custom selection callback.
   9512      */
   9513     public ActionMode.Callback getCustomSelectionActionModeCallback() {
   9514         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
   9515     }
   9516 
   9517     /**
   9518      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
   9519      * insertion is initiated in this View.
   9520      * The standard implementation populates the menu with a subset of Select All,
   9521      * Paste and Replace actions, depending on what this View supports.
   9522      *
   9523      * <p>A custom implementation can add new entries in the default menu in its
   9524      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
   9525      * android.view.Menu)} method. The default actions can also be removed from the menu using
   9526      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
   9527      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
   9528      *
   9529      * <p>Returning false from
   9530      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
   9531      * android.view.Menu)} will prevent the action mode from being started.</p>
   9532      *
   9533      * <p>Action click events should be handled by the custom implementation of
   9534      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
   9535      * android.view.MenuItem)}.</p>
   9536      *
   9537      * <p>Note that text insertion mode is not started when a TextView receives focus and the
   9538      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
   9539      */
   9540     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
   9541         createEditorIfNeeded();
   9542         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
   9543     }
   9544 
   9545     /**
   9546      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
   9547      *
   9548      * @return The current custom insertion callback.
   9549      */
   9550     public ActionMode.Callback getCustomInsertionActionModeCallback() {
   9551         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
   9552     }
   9553 
   9554     /**
   9555      * @hide
   9556      */
   9557     protected void stopTextActionMode() {
   9558         if (mEditor != null) {
   9559             mEditor.stopTextActionMode();
   9560         }
   9561     }
   9562 
   9563     boolean canUndo() {
   9564         return mEditor != null && mEditor.canUndo();
   9565     }
   9566 
   9567     boolean canRedo() {
   9568         return mEditor != null && mEditor.canRedo();
   9569     }
   9570 
   9571     boolean canCut() {
   9572         if (hasPasswordTransformationMethod()) {
   9573             return false;
   9574         }
   9575 
   9576         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
   9577                 mEditor.mKeyListener != null) {
   9578             return true;
   9579         }
   9580 
   9581         return false;
   9582     }
   9583 
   9584     boolean canCopy() {
   9585         if (hasPasswordTransformationMethod()) {
   9586             return false;
   9587         }
   9588 
   9589         if (mText.length() > 0 && hasSelection() && mEditor != null) {
   9590             return true;
   9591         }
   9592 
   9593         return false;
   9594     }
   9595 
   9596     boolean canShare() {
   9597         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
   9598             return false;
   9599         }
   9600         return canCopy();
   9601     }
   9602 
   9603     boolean isDeviceProvisioned() {
   9604         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
   9605             mDeviceProvisionedState = Settings.Global.getInt(
   9606                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
   9607                     ? DEVICE_PROVISIONED_YES
   9608                     : DEVICE_PROVISIONED_NO;
   9609         }
   9610         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
   9611     }
   9612 
   9613     boolean canPaste() {
   9614         return (mText instanceof Editable &&
   9615                 mEditor != null && mEditor.mKeyListener != null &&
   9616                 getSelectionStart() >= 0 &&
   9617                 getSelectionEnd() >= 0 &&
   9618                 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
   9619                 hasPrimaryClip());
   9620     }
   9621 
   9622     boolean canProcessText() {
   9623         if (getId() == View.NO_ID) {
   9624             return false;
   9625         }
   9626         return canShare();
   9627     }
   9628 
   9629     boolean canSelectAllText() {
   9630         return canSelectText() && !hasPasswordTransformationMethod()
   9631                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
   9632     }
   9633 
   9634     boolean selectAllText() {
   9635         final int length = mText.length();
   9636         Selection.setSelection((Spannable) mText, 0, length);
   9637         return length > 0;
   9638     }
   9639 
   9640     void replaceSelectionWithText(CharSequence text) {
   9641         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
   9642     }
   9643 
   9644     /**
   9645      * Paste clipboard content between min and max positions.
   9646      */
   9647     private void paste(int min, int max, boolean withFormatting) {
   9648         ClipboardManager clipboard =
   9649             (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
   9650         ClipData clip = clipboard.getPrimaryClip();
   9651         if (clip != null) {
   9652             boolean didFirst = false;
   9653             for (int i=0; i<clip.getItemCount(); i++) {
   9654                 final CharSequence paste;
   9655                 if (withFormatting) {
   9656                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
   9657                 } else {
   9658                     // Get an item as text and remove all spans by toString().
   9659                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
   9660                     paste = (text instanceof Spanned) ? text.toString() : text;
   9661                 }
   9662                 if (paste != null) {
   9663                     if (!didFirst) {
   9664                         Selection.setSelection((Spannable) mText, max);
   9665                         ((Editable) mText).replace(min, max, paste);
   9666                         didFirst = true;
   9667                     } else {
   9668                         ((Editable) mText).insert(getSelectionEnd(), "\n");
   9669                         ((Editable) mText).insert(getSelectionEnd(), paste);
   9670                     }
   9671                 }
   9672             }
   9673             sLastCutCopyOrTextChangedTime = 0;
   9674         }
   9675     }
   9676 
   9677     private void shareSelectedText() {
   9678         String selectedText = getSelectedText();
   9679         if (selectedText != null && !selectedText.isEmpty()) {
   9680             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
   9681             sharingIntent.setType("text/plain");
   9682             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
   9683             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
   9684             getContext().startActivity(Intent.createChooser(sharingIntent, null));
   9685             Selection.setSelection((Spannable) mText, getSelectionEnd());
   9686         }
   9687     }
   9688 
   9689     private void setPrimaryClip(ClipData clip) {
   9690         ClipboardManager clipboard = (ClipboardManager) getContext().
   9691                 getSystemService(Context.CLIPBOARD_SERVICE);
   9692         clipboard.setPrimaryClip(clip);
   9693         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
   9694     }
   9695 
   9696     /**
   9697      * Get the character offset closest to the specified absolute position. A typical use case is to
   9698      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
   9699      *
   9700      * @param x The horizontal absolute position of a point on screen
   9701      * @param y The vertical absolute position of a point on screen
   9702      * @return the character offset for the character whose position is closest to the specified
   9703      *  position. Returns -1 if there is no layout.
   9704      */
   9705     public int getOffsetForPosition(float x, float y) {
   9706         if (getLayout() == null) return -1;
   9707         final int line = getLineAtCoordinate(y);
   9708         final int offset = getOffsetAtCoordinate(line, x);
   9709         return offset;
   9710     }
   9711 
   9712     float convertToLocalHorizontalCoordinate(float x) {
   9713         x -= getTotalPaddingLeft();
   9714         // Clamp the position to inside of the view.
   9715         x = Math.max(0.0f, x);
   9716         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
   9717         x += getScrollX();
   9718         return x;
   9719     }
   9720 
   9721     int getLineAtCoordinate(float y) {
   9722         y -= getTotalPaddingTop();
   9723         // Clamp the position to inside of the view.
   9724         y = Math.max(0.0f, y);
   9725         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
   9726         y += getScrollY();
   9727         return getLayout().getLineForVertical((int) y);
   9728     }
   9729 
   9730     int getLineAtCoordinateUnclamped(float y) {
   9731         y -= getTotalPaddingTop();
   9732         y += getScrollY();
   9733         return getLayout().getLineForVertical((int) y);
   9734     }
   9735 
   9736     int getOffsetAtCoordinate(int line, float x) {
   9737         x = convertToLocalHorizontalCoordinate(x);
   9738         return getLayout().getOffsetForHorizontal(line, x);
   9739     }
   9740 
   9741     @Override
   9742     public boolean onDragEvent(DragEvent event) {
   9743         switch (event.getAction()) {
   9744             case DragEvent.ACTION_DRAG_STARTED:
   9745                 return mEditor != null && mEditor.hasInsertionController();
   9746 
   9747             case DragEvent.ACTION_DRAG_ENTERED:
   9748                 TextView.this.requestFocus();
   9749                 return true;
   9750 
   9751             case DragEvent.ACTION_DRAG_LOCATION:
   9752                 final int offset = getOffsetForPosition(event.getX(), event.getY());
   9753                 Selection.setSelection((Spannable)mText, offset);
   9754                 return true;
   9755 
   9756             case DragEvent.ACTION_DROP:
   9757                 if (mEditor != null) mEditor.onDrop(event);
   9758                 return true;
   9759 
   9760             case DragEvent.ACTION_DRAG_ENDED:
   9761             case DragEvent.ACTION_DRAG_EXITED:
   9762             default:
   9763                 return true;
   9764         }
   9765     }
   9766 
   9767     boolean isInBatchEditMode() {
   9768         if (mEditor == null) return false;
   9769         final Editor.InputMethodState ims = mEditor.mInputMethodState;
   9770         if (ims != null) {
   9771             return ims.mBatchEditNesting > 0;
   9772         }
   9773         return mEditor.mInBatchEditControllers;
   9774     }
   9775 
   9776     @Override
   9777     public void onRtlPropertiesChanged(int layoutDirection) {
   9778         super.onRtlPropertiesChanged(layoutDirection);
   9779 
   9780         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
   9781         if (mTextDir != newTextDir) {
   9782             mTextDir = newTextDir;
   9783             if (mLayout != null) {
   9784                 checkForRelayout();
   9785             }
   9786         }
   9787     }
   9788 
   9789     /**
   9790      * @hide
   9791      */
   9792     protected TextDirectionHeuristic getTextDirectionHeuristic() {
   9793         if (hasPasswordTransformationMethod()) {
   9794             // passwords fields should be LTR
   9795             return TextDirectionHeuristics.LTR;
   9796         }
   9797 
   9798         // Always need to resolve layout direction first
   9799         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
   9800 
   9801         // Now, we can select the heuristic
   9802         switch (getTextDirection()) {
   9803             default:
   9804             case TEXT_DIRECTION_FIRST_STRONG:
   9805                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
   9806                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
   9807             case TEXT_DIRECTION_ANY_RTL:
   9808                 return TextDirectionHeuristics.ANYRTL_LTR;
   9809             case TEXT_DIRECTION_LTR:
   9810                 return TextDirectionHeuristics.LTR;
   9811             case TEXT_DIRECTION_RTL:
   9812                 return TextDirectionHeuristics.RTL;
   9813             case TEXT_DIRECTION_LOCALE:
   9814                 return TextDirectionHeuristics.LOCALE;
   9815             case TEXT_DIRECTION_FIRST_STRONG_LTR:
   9816                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
   9817             case TEXT_DIRECTION_FIRST_STRONG_RTL:
   9818                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
   9819         }
   9820     }
   9821 
   9822     /**
   9823      * @hide
   9824      */
   9825     @Override
   9826     public void onResolveDrawables(int layoutDirection) {
   9827         // No need to resolve twice
   9828         if (mLastLayoutDirection == layoutDirection) {
   9829             return;
   9830         }
   9831         mLastLayoutDirection = layoutDirection;
   9832 
   9833         // Resolve drawables
   9834         if (mDrawables != null) {
   9835             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
   9836                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
   9837                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
   9838                 applyCompoundDrawableTint();
   9839             }
   9840         }
   9841     }
   9842 
   9843     /**
   9844      * Prepares a drawable for display by propagating layout direction and
   9845      * drawable state.
   9846      *
   9847      * @param dr the drawable to prepare
   9848      */
   9849     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
   9850         if (dr == null) {
   9851             return;
   9852         }
   9853 
   9854         dr.setLayoutDirection(getLayoutDirection());
   9855 
   9856         if (dr.isStateful()) {
   9857             dr.setState(getDrawableState());
   9858             dr.jumpToCurrentState();
   9859         }
   9860     }
   9861 
   9862     /**
   9863      * @hide
   9864      */
   9865     protected void resetResolvedDrawables() {
   9866         super.resetResolvedDrawables();
   9867         mLastLayoutDirection = -1;
   9868     }
   9869 
   9870     /**
   9871      * @hide
   9872      */
   9873     protected void viewClicked(InputMethodManager imm) {
   9874         if (imm != null) {
   9875             imm.viewClicked(this);
   9876         }
   9877     }
   9878 
   9879     /**
   9880      * Deletes the range of text [start, end[.
   9881      * @hide
   9882      */
   9883     protected void deleteText_internal(int start, int end) {
   9884         ((Editable) mText).delete(start, end);
   9885     }
   9886 
   9887     /**
   9888      * Replaces the range of text [start, end[ by replacement text
   9889      * @hide
   9890      */
   9891     protected void replaceText_internal(int start, int end, CharSequence text) {
   9892         ((Editable) mText).replace(start, end, text);
   9893     }
   9894 
   9895     /**
   9896      * Sets a span on the specified range of text
   9897      * @hide
   9898      */
   9899     protected void setSpan_internal(Object span, int start, int end, int flags) {
   9900         ((Editable) mText).setSpan(span, start, end, flags);
   9901     }
   9902 
   9903     /**
   9904      * Moves the cursor to the specified offset position in text
   9905      * @hide
   9906      */
   9907     protected void setCursorPosition_internal(int start, int end) {
   9908         Selection.setSelection(((Editable) mText), start, end);
   9909     }
   9910 
   9911     /**
   9912      * An Editor should be created as soon as any of the editable-specific fields (grouped
   9913      * inside the Editor object) is assigned to a non-default value.
   9914      * This method will create the Editor if needed.
   9915      *
   9916      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
   9917      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
   9918      * Editor for backward compatibility, as soon as one of these fields is assigned.
   9919      *
   9920      * Also note that for performance reasons, the mEditor is created when needed, but not
   9921      * reset when no more edit-specific fields are needed.
   9922      */
   9923     private void createEditorIfNeeded() {
   9924         if (mEditor == null) {
   9925             mEditor = new Editor(this);
   9926         }
   9927     }
   9928 
   9929     /**
   9930      * @hide
   9931      */
   9932     @Override
   9933     public CharSequence getIterableTextForAccessibility() {
   9934         return mText;
   9935     }
   9936 
   9937     private void ensureIterableTextForAccessibilitySelectable() {
   9938         if (!(mText instanceof Spannable)) {
   9939             setText(mText, BufferType.SPANNABLE);
   9940         }
   9941     }
   9942 
   9943     /**
   9944      * @hide
   9945      */
   9946     @Override
   9947     public TextSegmentIterator getIteratorForGranularity(int granularity) {
   9948         switch (granularity) {
   9949             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
   9950                 Spannable text = (Spannable) getIterableTextForAccessibility();
   9951                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
   9952                     AccessibilityIterators.LineTextSegmentIterator iterator =
   9953                         AccessibilityIterators.LineTextSegmentIterator.getInstance();
   9954                     iterator.initialize(text, getLayout());
   9955                     return iterator;
   9956                 }
   9957             } break;
   9958             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
   9959                 Spannable text = (Spannable) getIterableTextForAccessibility();
   9960                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
   9961                     AccessibilityIterators.PageTextSegmentIterator iterator =
   9962                         AccessibilityIterators.PageTextSegmentIterator.getInstance();
   9963                     iterator.initialize(this);
   9964                     return iterator;
   9965                 }
   9966             } break;
   9967         }
   9968         return super.getIteratorForGranularity(granularity);
   9969     }
   9970 
   9971     /**
   9972      * @hide
   9973      */
   9974     @Override
   9975     public int getAccessibilitySelectionStart() {
   9976         return getSelectionStart();
   9977     }
   9978 
   9979     /**
   9980      * @hide
   9981      */
   9982     public boolean isAccessibilitySelectionExtendable() {
   9983         return true;
   9984     }
   9985 
   9986     /**
   9987      * @hide
   9988      */
   9989     @Override
   9990     public int getAccessibilitySelectionEnd() {
   9991         return getSelectionEnd();
   9992     }
   9993 
   9994     /**
   9995      * @hide
   9996      */
   9997     @Override
   9998     public void setAccessibilitySelection(int start, int end) {
   9999         if (getAccessibilitySelectionStart() == start
   10000                 && getAccessibilitySelectionEnd() == end) {
   10001             return;
   10002         }
   10003         CharSequence text = getIterableTextForAccessibility();
   10004         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
   10005             Selection.setSelection((Spannable) text, start, end);
   10006         } else {
   10007             Selection.removeSelection((Spannable) text);
   10008         }
   10009         // Hide all selection controllers used for adjusting selection
   10010         // since we are doing so explicitlty by other means and these
   10011         // controllers interact with how selection behaves.
   10012         if (mEditor != null) {
   10013             mEditor.hideCursorAndSpanControllers();
   10014             mEditor.stopTextActionMode();
   10015         }
   10016     }
   10017 
   10018     /** @hide */
   10019     @Override
   10020     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
   10021         super.encodeProperties(stream);
   10022 
   10023         TruncateAt ellipsize = getEllipsize();
   10024         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
   10025         stream.addProperty("text:textSize", getTextSize());
   10026         stream.addProperty("text:scaledTextSize", getScaledTextSize());
   10027         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
   10028         stream.addProperty("text:selectionStart", getSelectionStart());
   10029         stream.addProperty("text:selectionEnd", getSelectionEnd());
   10030         stream.addProperty("text:curTextColor", mCurTextColor);
   10031         stream.addProperty("text:text", mText == null ? null : mText.toString());
   10032         stream.addProperty("text:gravity", mGravity);
   10033     }
   10034 
   10035     /**
   10036      * User interface state that is stored by TextView for implementing
   10037      * {@link View#onSaveInstanceState}.
   10038      */
   10039     public static class SavedState extends BaseSavedState {
   10040         int selStart = -1;
   10041         int selEnd = -1;
   10042         CharSequence text;
   10043         boolean frozenWithFocus;
   10044         CharSequence error;
   10045         ParcelableParcel editorState;  // Optional state from Editor.
   10046 
   10047         SavedState(Parcelable superState) {
   10048             super(superState);
   10049         }
   10050 
   10051         @Override
   10052         public void writeToParcel(Parcel out, int flags) {
   10053             super.writeToParcel(out, flags);
   10054             out.writeInt(selStart);
   10055             out.writeInt(selEnd);
   10056             out.writeInt(frozenWithFocus ? 1 : 0);
   10057             TextUtils.writeToParcel(text, out, flags);
   10058 
   10059             if (error == null) {
   10060                 out.writeInt(0);
   10061             } else {
   10062                 out.writeInt(1);
   10063                 TextUtils.writeToParcel(error, out, flags);
   10064             }
   10065 
   10066             if (editorState == null) {
   10067                 out.writeInt(0);
   10068             } else {
   10069                 out.writeInt(1);
   10070                 editorState.writeToParcel(out, flags);
   10071             }
   10072         }
   10073 
   10074         @Override
   10075         public String toString() {
   10076             String str = "TextView.SavedState{"
   10077                     + Integer.toHexString(System.identityHashCode(this))
   10078                     + " start=" + selStart + " end=" + selEnd;
   10079             if (text != null) {
   10080                 str += " text=" + text;
   10081             }
   10082             return str + "}";
   10083         }
   10084 
   10085         @SuppressWarnings("hiding")
   10086         public static final Parcelable.Creator<SavedState> CREATOR
   10087                 = new Parcelable.Creator<SavedState>() {
   10088             public SavedState createFromParcel(Parcel in) {
   10089                 return new SavedState(in);
   10090             }
   10091 
   10092             public SavedState[] newArray(int size) {
   10093                 return new SavedState[size];
   10094             }
   10095         };
   10096 
   10097         private SavedState(Parcel in) {
   10098             super(in);
   10099             selStart = in.readInt();
   10100             selEnd = in.readInt();
   10101             frozenWithFocus = (in.readInt() != 0);
   10102             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   10103 
   10104             if (in.readInt() != 0) {
   10105                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   10106             }
   10107 
   10108             if (in.readInt() != 0) {
   10109                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
   10110             }
   10111         }
   10112     }
   10113 
   10114     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
   10115         private char[] mChars;
   10116         private int mStart, mLength;
   10117 
   10118         public CharWrapper(char[] chars, int start, int len) {
   10119             mChars = chars;
   10120             mStart = start;
   10121             mLength = len;
   10122         }
   10123 
   10124         /* package */ void set(char[] chars, int start, int len) {
   10125             mChars = chars;
   10126             mStart = start;
   10127             mLength = len;
   10128         }
   10129 
   10130         public int length() {
   10131             return mLength;
   10132         }
   10133 
   10134         public char charAt(int off) {
   10135             return mChars[off + mStart];
   10136         }
   10137 
   10138         @Override
   10139         public String toString() {
   10140             return new String(mChars, mStart, mLength);
   10141         }
   10142 
   10143         public CharSequence subSequence(int start, int end) {
   10144             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   10145                 throw new IndexOutOfBoundsException(start + ", " + end);
   10146             }
   10147 
   10148             return new String(mChars, start + mStart, end - start);
   10149         }
   10150 
   10151         public void getChars(int start, int end, char[] buf, int off) {
   10152             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   10153                 throw new IndexOutOfBoundsException(start + ", " + end);
   10154             }
   10155 
   10156             System.arraycopy(mChars, start + mStart, buf, off, end - start);
   10157         }
   10158 
   10159         public void drawText(Canvas c, int start, int end,
   10160                              float x, float y, Paint p) {
   10161             c.drawText(mChars, start + mStart, end - start, x, y, p);
   10162         }
   10163 
   10164         public void drawTextRun(Canvas c, int start, int end,
   10165                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
   10166             int count = end - start;
   10167             int contextCount = contextEnd - contextStart;
   10168             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
   10169                     contextCount, x, y, isRtl, p);
   10170         }
   10171 
   10172         public float measureText(int start, int end, Paint p) {
   10173             return p.measureText(mChars, start + mStart, end - start);
   10174         }
   10175 
   10176         public int getTextWidths(int start, int end, float[] widths, Paint p) {
   10177             return p.getTextWidths(mChars, start + mStart, end - start, widths);
   10178         }
   10179 
   10180         public float getTextRunAdvances(int start, int end, int contextStart,
   10181                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
   10182                 Paint p) {
   10183             int count = end - start;
   10184             int contextCount = contextEnd - contextStart;
   10185             return p.getTextRunAdvances(mChars, start + mStart, count,
   10186                     contextStart + mStart, contextCount, isRtl, advances,
   10187                     advancesIndex);
   10188         }
   10189 
   10190         public int getTextRunCursor(int contextStart, int contextEnd, int dir,
   10191                 int offset, int cursorOpt, Paint p) {
   10192             int contextCount = contextEnd - contextStart;
   10193             return p.getTextRunCursor(mChars, contextStart + mStart,
   10194                     contextCount, dir, offset + mStart, cursorOpt);
   10195         }
   10196     }
   10197 
   10198     private static final class Marquee {
   10199         // TODO: Add an option to configure this
   10200         private static final float MARQUEE_DELTA_MAX = 0.07f;
   10201         private static final int MARQUEE_DELAY = 1200;
   10202         private static final int MARQUEE_DP_PER_SECOND = 30;
   10203 
   10204         private static final byte MARQUEE_STOPPED = 0x0;
   10205         private static final byte MARQUEE_STARTING = 0x1;
   10206         private static final byte MARQUEE_RUNNING = 0x2;
   10207 
   10208         private final WeakReference<TextView> mView;
   10209         private final Choreographer mChoreographer;
   10210 
   10211         private byte mStatus = MARQUEE_STOPPED;
   10212         private final float mPixelsPerSecond;
   10213         private float mMaxScroll;
   10214         private float mMaxFadeScroll;
   10215         private float mGhostStart;
   10216         private float mGhostOffset;
   10217         private float mFadeStop;
   10218         private int mRepeatLimit;
   10219 
   10220         private float mScroll;
   10221         private long mLastAnimationMs;
   10222 
   10223         Marquee(TextView v) {
   10224             final float density = v.getContext().getResources().getDisplayMetrics().density;
   10225             mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
   10226             mView = new WeakReference<TextView>(v);
   10227             mChoreographer = Choreographer.getInstance();
   10228         }
   10229 
   10230         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
   10231             @Override
   10232             public void doFrame(long frameTimeNanos) {
   10233                 tick();
   10234             }
   10235         };
   10236 
   10237         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
   10238             @Override
   10239             public void doFrame(long frameTimeNanos) {
   10240                 mStatus = MARQUEE_RUNNING;
   10241                 mLastAnimationMs = mChoreographer.getFrameTime();
   10242                 tick();
   10243             }
   10244         };
   10245 
   10246         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
   10247             @Override
   10248             public void doFrame(long frameTimeNanos) {
   10249                 if (mStatus == MARQUEE_RUNNING) {
   10250                     if (mRepeatLimit >= 0) {
   10251                         mRepeatLimit--;
   10252                     }
   10253                     start(mRepeatLimit);
   10254                 }
   10255             }
   10256         };
   10257 
   10258         void tick() {
   10259             if (mStatus != MARQUEE_RUNNING) {
   10260                 return;
   10261             }
   10262 
   10263             mChoreographer.removeFrameCallback(mTickCallback);
   10264 
   10265             final TextView textView = mView.get();
   10266             if (textView != null && (textView.isFocused() || textView.isSelected())) {
   10267                 long currentMs = mChoreographer.getFrameTime();
   10268                 long deltaMs = currentMs - mLastAnimationMs;
   10269                 mLastAnimationMs = currentMs;
   10270                 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
   10271                 mScroll += deltaPx;
   10272                 if (mScroll > mMaxScroll) {
   10273                     mScroll = mMaxScroll;
   10274                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
   10275                 } else {
   10276                     mChoreographer.postFrameCallback(mTickCallback);
   10277                 }
   10278                 textView.invalidate();
   10279             }
   10280         }
   10281 
   10282         void stop() {
   10283             mStatus = MARQUEE_STOPPED;
   10284             mChoreographer.removeFrameCallback(mStartCallback);
   10285             mChoreographer.removeFrameCallback(mRestartCallback);
   10286             mChoreographer.removeFrameCallback(mTickCallback);
   10287             resetScroll();
   10288         }
   10289 
   10290         private void resetScroll() {
   10291             mScroll = 0.0f;
   10292             final TextView textView = mView.get();
   10293             if (textView != null) textView.invalidate();
   10294         }
   10295 
   10296         void start(int repeatLimit) {
   10297             if (repeatLimit == 0) {
   10298                 stop();
   10299                 return;
   10300             }
   10301             mRepeatLimit = repeatLimit;
   10302             final TextView textView = mView.get();
   10303             if (textView != null && textView.mLayout != null) {
   10304                 mStatus = MARQUEE_STARTING;
   10305                 mScroll = 0.0f;
   10306                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
   10307                         textView.getCompoundPaddingRight();
   10308                 final float lineWidth = textView.mLayout.getLineWidth(0);
   10309                 final float gap = textWidth / 3.0f;
   10310                 mGhostStart = lineWidth - textWidth + gap;
   10311                 mMaxScroll = mGhostStart + textWidth;
   10312                 mGhostOffset = lineWidth + gap;
   10313                 mFadeStop = lineWidth + textWidth / 6.0f;
   10314                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
   10315 
   10316                 textView.invalidate();
   10317                 mChoreographer.postFrameCallback(mStartCallback);
   10318             }
   10319         }
   10320 
   10321         float getGhostOffset() {
   10322             return mGhostOffset;
   10323         }
   10324 
   10325         float getScroll() {
   10326             return mScroll;
   10327         }
   10328 
   10329         float getMaxFadeScroll() {
   10330             return mMaxFadeScroll;
   10331         }
   10332 
   10333         boolean shouldDrawLeftFade() {
   10334             return mScroll <= mFadeStop;
   10335         }
   10336 
   10337         boolean shouldDrawGhost() {
   10338             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
   10339         }
   10340 
   10341         boolean isRunning() {
   10342             return mStatus == MARQUEE_RUNNING;
   10343         }
   10344 
   10345         boolean isStopped() {
   10346             return mStatus == MARQUEE_STOPPED;
   10347         }
   10348     }
   10349 
   10350     private class ChangeWatcher implements TextWatcher, SpanWatcher {
   10351 
   10352         private CharSequence mBeforeText;
   10353 
   10354         public void beforeTextChanged(CharSequence buffer, int start,
   10355                                       int before, int after) {
   10356             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
   10357                     + " before=" + before + " after=" + after + ": " + buffer);
   10358 
   10359             if (AccessibilityManager.getInstance(mContext).isEnabled()
   10360                     && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
   10361                             || shouldSpeakPasswordsForAccessibility())) {
   10362                 mBeforeText = buffer.toString();
   10363             }
   10364 
   10365             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
   10366         }
   10367 
   10368         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
   10369             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
   10370                     + " before=" + before + " after=" + after + ": " + buffer);
   10371             TextView.this.handleTextChanged(buffer, start, before, after);
   10372 
   10373             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
   10374                     (isFocused() || isSelected() && isShown())) {
   10375                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
   10376                 mBeforeText = null;
   10377             }
   10378         }
   10379 
   10380         public void afterTextChanged(Editable buffer) {
   10381             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
   10382             TextView.this.sendAfterTextChanged(buffer);
   10383 
   10384             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
   10385                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
   10386             }
   10387         }
   10388 
   10389         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
   10390             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
   10391                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
   10392             TextView.this.spanChange(buf, what, s, st, e, en);
   10393         }
   10394 
   10395         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
   10396             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
   10397                     + " what=" + what + ": " + buf);
   10398             TextView.this.spanChange(buf, what, -1, s, -1, e);
   10399         }
   10400 
   10401         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
   10402             if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
   10403                     + " what=" + what + ": " + buf);
   10404             TextView.this.spanChange(buf, what, s, -1, e, -1);
   10405         }
   10406     }
   10407 }
   10408