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