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