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