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.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
     20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
     21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
     22 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
     23 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
     24 
     25 import android.R;
     26 import android.annotation.CheckResult;
     27 import android.annotation.ColorInt;
     28 import android.annotation.DrawableRes;
     29 import android.annotation.FloatRange;
     30 import android.annotation.IntDef;
     31 import android.annotation.IntRange;
     32 import android.annotation.NonNull;
     33 import android.annotation.Nullable;
     34 import android.annotation.Px;
     35 import android.annotation.RequiresPermission;
     36 import android.annotation.Size;
     37 import android.annotation.StringRes;
     38 import android.annotation.StyleRes;
     39 import android.annotation.UnsupportedAppUsage;
     40 import android.annotation.XmlRes;
     41 import android.app.Activity;
     42 import android.app.PendingIntent;
     43 import android.app.assist.AssistStructure;
     44 import android.content.ClipData;
     45 import android.content.ClipDescription;
     46 import android.content.ClipboardManager;
     47 import android.content.Context;
     48 import android.content.Intent;
     49 import android.content.UndoManager;
     50 import android.content.pm.PackageManager;
     51 import android.content.res.ColorStateList;
     52 import android.content.res.CompatibilityInfo;
     53 import android.content.res.Configuration;
     54 import android.content.res.Resources;
     55 import android.content.res.TypedArray;
     56 import android.content.res.XmlResourceParser;
     57 import android.graphics.BaseCanvas;
     58 import android.graphics.BlendMode;
     59 import android.graphics.Canvas;
     60 import android.graphics.Insets;
     61 import android.graphics.Paint;
     62 import android.graphics.Paint.FontMetricsInt;
     63 import android.graphics.Path;
     64 import android.graphics.PorterDuff;
     65 import android.graphics.Rect;
     66 import android.graphics.RectF;
     67 import android.graphics.Typeface;
     68 import android.graphics.drawable.Drawable;
     69 import android.graphics.fonts.FontStyle;
     70 import android.graphics.fonts.FontVariationAxis;
     71 import android.icu.text.DecimalFormatSymbols;
     72 import android.os.AsyncTask;
     73 import android.os.Build;
     74 import android.os.Build.VERSION_CODES;
     75 import android.os.Bundle;
     76 import android.os.LocaleList;
     77 import android.os.Parcel;
     78 import android.os.Parcelable;
     79 import android.os.ParcelableParcel;
     80 import android.os.Process;
     81 import android.os.SystemClock;
     82 import android.os.UserHandle;
     83 import android.provider.Settings;
     84 import android.text.BoringLayout;
     85 import android.text.DynamicLayout;
     86 import android.text.Editable;
     87 import android.text.GetChars;
     88 import android.text.GraphicsOperations;
     89 import android.text.InputFilter;
     90 import android.text.InputType;
     91 import android.text.Layout;
     92 import android.text.ParcelableSpan;
     93 import android.text.PrecomputedText;
     94 import android.text.Selection;
     95 import android.text.SpanWatcher;
     96 import android.text.Spannable;
     97 import android.text.SpannableStringBuilder;
     98 import android.text.Spanned;
     99 import android.text.SpannedString;
    100 import android.text.StaticLayout;
    101 import android.text.TextDirectionHeuristic;
    102 import android.text.TextDirectionHeuristics;
    103 import android.text.TextPaint;
    104 import android.text.TextUtils;
    105 import android.text.TextUtils.TruncateAt;
    106 import android.text.TextWatcher;
    107 import android.text.method.AllCapsTransformationMethod;
    108 import android.text.method.ArrowKeyMovementMethod;
    109 import android.text.method.DateKeyListener;
    110 import android.text.method.DateTimeKeyListener;
    111 import android.text.method.DialerKeyListener;
    112 import android.text.method.DigitsKeyListener;
    113 import android.text.method.KeyListener;
    114 import android.text.method.LinkMovementMethod;
    115 import android.text.method.MetaKeyKeyListener;
    116 import android.text.method.MovementMethod;
    117 import android.text.method.PasswordTransformationMethod;
    118 import android.text.method.SingleLineTransformationMethod;
    119 import android.text.method.TextKeyListener;
    120 import android.text.method.TimeKeyListener;
    121 import android.text.method.TransformationMethod;
    122 import android.text.method.TransformationMethod2;
    123 import android.text.method.WordIterator;
    124 import android.text.style.CharacterStyle;
    125 import android.text.style.ClickableSpan;
    126 import android.text.style.ParagraphStyle;
    127 import android.text.style.SpellCheckSpan;
    128 import android.text.style.SuggestionSpan;
    129 import android.text.style.URLSpan;
    130 import android.text.style.UpdateAppearance;
    131 import android.text.util.Linkify;
    132 import android.util.AttributeSet;
    133 import android.util.DisplayMetrics;
    134 import android.util.IntArray;
    135 import android.util.Log;
    136 import android.util.SparseIntArray;
    137 import android.util.TypedValue;
    138 import android.view.AccessibilityIterators.TextSegmentIterator;
    139 import android.view.ActionMode;
    140 import android.view.Choreographer;
    141 import android.view.ContextMenu;
    142 import android.view.DragEvent;
    143 import android.view.Gravity;
    144 import android.view.HapticFeedbackConstants;
    145 import android.view.InputDevice;
    146 import android.view.KeyCharacterMap;
    147 import android.view.KeyEvent;
    148 import android.view.MotionEvent;
    149 import android.view.PointerIcon;
    150 import android.view.View;
    151 import android.view.ViewConfiguration;
    152 import android.view.ViewDebug;
    153 import android.view.ViewGroup.LayoutParams;
    154 import android.view.ViewHierarchyEncoder;
    155 import android.view.ViewParent;
    156 import android.view.ViewRootImpl;
    157 import android.view.ViewStructure;
    158 import android.view.ViewTreeObserver;
    159 import android.view.accessibility.AccessibilityEvent;
    160 import android.view.accessibility.AccessibilityManager;
    161 import android.view.accessibility.AccessibilityNodeInfo;
    162 import android.view.animation.AnimationUtils;
    163 import android.view.autofill.AutofillManager;
    164 import android.view.autofill.AutofillValue;
    165 import android.view.inputmethod.BaseInputConnection;
    166 import android.view.inputmethod.CompletionInfo;
    167 import android.view.inputmethod.CorrectionInfo;
    168 import android.view.inputmethod.CursorAnchorInfo;
    169 import android.view.inputmethod.EditorInfo;
    170 import android.view.inputmethod.ExtractedText;
    171 import android.view.inputmethod.ExtractedTextRequest;
    172 import android.view.inputmethod.InputConnection;
    173 import android.view.inputmethod.InputMethodManager;
    174 import android.view.inspector.InspectableProperty;
    175 import android.view.inspector.InspectableProperty.EnumEntry;
    176 import android.view.inspector.InspectableProperty.FlagEntry;
    177 import android.view.textclassifier.TextClassification;
    178 import android.view.textclassifier.TextClassificationContext;
    179 import android.view.textclassifier.TextClassificationManager;
    180 import android.view.textclassifier.TextClassifier;
    181 import android.view.textclassifier.TextLinks;
    182 import android.view.textservice.SpellCheckerSubtype;
    183 import android.view.textservice.TextServicesManager;
    184 import android.widget.RemoteViews.RemoteView;
    185 
    186 import com.android.internal.annotations.VisibleForTesting;
    187 import com.android.internal.logging.MetricsLogger;
    188 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
    189 import com.android.internal.util.FastMath;
    190 import com.android.internal.util.Preconditions;
    191 import com.android.internal.widget.EditableInputConnection;
    192 
    193 import libcore.util.EmptyArray;
    194 
    195 import org.xmlpull.v1.XmlPullParserException;
    196 
    197 import java.io.IOException;
    198 import java.lang.annotation.Retention;
    199 import java.lang.annotation.RetentionPolicy;
    200 import java.lang.ref.WeakReference;
    201 import java.util.ArrayList;
    202 import java.util.Arrays;
    203 import java.util.Locale;
    204 import java.util.Objects;
    205 import java.util.concurrent.CompletableFuture;
    206 import java.util.concurrent.TimeUnit;
    207 import java.util.function.Consumer;
    208 import java.util.function.Supplier;
    209 
    210 /**
    211  * A user interface element that displays text to the user.
    212  * To provide user-editable text, see {@link EditText}.
    213  * <p>
    214  * The following code sample shows a typical use, with an XML layout
    215  * and code to modify the contents of the text view:
    216  * </p>
    217 
    218  * <pre>
    219  * &lt;LinearLayout
    220        xmlns:android="http://schemas.android.com/apk/res/android"
    221        android:layout_width="match_parent"
    222        android:layout_height="match_parent"&gt;
    223  *    &lt;TextView
    224  *        android:id="@+id/text_view_id"
    225  *        android:layout_height="wrap_content"
    226  *        android:layout_width="wrap_content"
    227  *        android:text="@string/hello" /&gt;
    228  * &lt;/LinearLayout&gt;
    229  * </pre>
    230  * <p>
    231  * This code sample demonstrates how to modify the contents of the text view
    232  * defined in the previous XML layout:
    233  * </p>
    234  * <pre>
    235  * public class MainActivity extends Activity {
    236  *
    237  *    protected void onCreate(Bundle savedInstanceState) {
    238  *         super.onCreate(savedInstanceState);
    239  *         setContentView(R.layout.activity_main);
    240  *         final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
    241  *         helloTextView.setText(R.string.user_greeting);
    242  *     }
    243  * }
    244  * </pre>
    245  * <p>
    246  * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
    247  * </p>
    248  * <p>
    249  * <b>XML attributes</b>
    250  * <p>
    251  * See {@link android.R.styleable#TextView TextView Attributes},
    252  * {@link android.R.styleable#View View Attributes}
    253  *
    254  * @attr ref android.R.styleable#TextView_text
    255  * @attr ref android.R.styleable#TextView_bufferType
    256  * @attr ref android.R.styleable#TextView_hint
    257  * @attr ref android.R.styleable#TextView_textColor
    258  * @attr ref android.R.styleable#TextView_textColorHighlight
    259  * @attr ref android.R.styleable#TextView_textColorHint
    260  * @attr ref android.R.styleable#TextView_textAppearance
    261  * @attr ref android.R.styleable#TextView_textColorLink
    262  * @attr ref android.R.styleable#TextView_textFontWeight
    263  * @attr ref android.R.styleable#TextView_textSize
    264  * @attr ref android.R.styleable#TextView_textScaleX
    265  * @attr ref android.R.styleable#TextView_fontFamily
    266  * @attr ref android.R.styleable#TextView_typeface
    267  * @attr ref android.R.styleable#TextView_textStyle
    268  * @attr ref android.R.styleable#TextView_cursorVisible
    269  * @attr ref android.R.styleable#TextView_maxLines
    270  * @attr ref android.R.styleable#TextView_maxHeight
    271  * @attr ref android.R.styleable#TextView_lines
    272  * @attr ref android.R.styleable#TextView_height
    273  * @attr ref android.R.styleable#TextView_minLines
    274  * @attr ref android.R.styleable#TextView_minHeight
    275  * @attr ref android.R.styleable#TextView_maxEms
    276  * @attr ref android.R.styleable#TextView_maxWidth
    277  * @attr ref android.R.styleable#TextView_ems
    278  * @attr ref android.R.styleable#TextView_width
    279  * @attr ref android.R.styleable#TextView_minEms
    280  * @attr ref android.R.styleable#TextView_minWidth
    281  * @attr ref android.R.styleable#TextView_gravity
    282  * @attr ref android.R.styleable#TextView_scrollHorizontally
    283  * @attr ref android.R.styleable#TextView_password
    284  * @attr ref android.R.styleable#TextView_singleLine
    285  * @attr ref android.R.styleable#TextView_selectAllOnFocus
    286  * @attr ref android.R.styleable#TextView_includeFontPadding
    287  * @attr ref android.R.styleable#TextView_maxLength
    288  * @attr ref android.R.styleable#TextView_shadowColor
    289  * @attr ref android.R.styleable#TextView_shadowDx
    290  * @attr ref android.R.styleable#TextView_shadowDy
    291  * @attr ref android.R.styleable#TextView_shadowRadius
    292  * @attr ref android.R.styleable#TextView_autoLink
    293  * @attr ref android.R.styleable#TextView_linksClickable
    294  * @attr ref android.R.styleable#TextView_numeric
    295  * @attr ref android.R.styleable#TextView_digits
    296  * @attr ref android.R.styleable#TextView_phoneNumber
    297  * @attr ref android.R.styleable#TextView_inputMethod
    298  * @attr ref android.R.styleable#TextView_capitalize
    299  * @attr ref android.R.styleable#TextView_autoText
    300  * @attr ref android.R.styleable#TextView_editable
    301  * @attr ref android.R.styleable#TextView_freezesText
    302  * @attr ref android.R.styleable#TextView_ellipsize
    303  * @attr ref android.R.styleable#TextView_drawableTop
    304  * @attr ref android.R.styleable#TextView_drawableBottom
    305  * @attr ref android.R.styleable#TextView_drawableRight
    306  * @attr ref android.R.styleable#TextView_drawableLeft
    307  * @attr ref android.R.styleable#TextView_drawableStart
    308  * @attr ref android.R.styleable#TextView_drawableEnd
    309  * @attr ref android.R.styleable#TextView_drawablePadding
    310  * @attr ref android.R.styleable#TextView_drawableTint
    311  * @attr ref android.R.styleable#TextView_drawableTintMode
    312  * @attr ref android.R.styleable#TextView_lineSpacingExtra
    313  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
    314  * @attr ref android.R.styleable#TextView_justificationMode
    315  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
    316  * @attr ref android.R.styleable#TextView_inputType
    317  * @attr ref android.R.styleable#TextView_imeOptions
    318  * @attr ref android.R.styleable#TextView_privateImeOptions
    319  * @attr ref android.R.styleable#TextView_imeActionLabel
    320  * @attr ref android.R.styleable#TextView_imeActionId
    321  * @attr ref android.R.styleable#TextView_editorExtras
    322  * @attr ref android.R.styleable#TextView_elegantTextHeight
    323  * @attr ref android.R.styleable#TextView_fallbackLineSpacing
    324  * @attr ref android.R.styleable#TextView_letterSpacing
    325  * @attr ref android.R.styleable#TextView_fontFeatureSettings
    326  * @attr ref android.R.styleable#TextView_fontVariationSettings
    327  * @attr ref android.R.styleable#TextView_breakStrategy
    328  * @attr ref android.R.styleable#TextView_hyphenationFrequency
    329  * @attr ref android.R.styleable#TextView_autoSizeTextType
    330  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
    331  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
    332  * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
    333  * @attr ref android.R.styleable#TextView_autoSizePresetSizes
    334  * @attr ref android.R.styleable#TextView_textCursorDrawable
    335  * @attr ref android.R.styleable#TextView_textSelectHandle
    336  * @attr ref android.R.styleable#TextView_textSelectHandleLeft
    337  * @attr ref android.R.styleable#TextView_textSelectHandleRight
    338  * @attr ref android.R.styleable#TextView_allowUndo
    339  * @attr ref android.R.styleable#TextView_enabled
    340  */
    341 @RemoteView
    342 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
    343     static final String LOG_TAG = "TextView";
    344     static final boolean DEBUG_EXTRACT = false;
    345     private static final float[] TEMP_POSITION = new float[2];
    346 
    347     // Enum for the "typeface" XML parameter.
    348     // TODO: How can we get this from the XML instead of hardcoding it here?
    349     /** @hide */
    350     @IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE})
    351     @Retention(RetentionPolicy.SOURCE)
    352     public @interface XMLTypefaceAttr{}
    353     private static final int DEFAULT_TYPEFACE = -1;
    354     private static final int SANS = 1;
    355     private static final int SERIF = 2;
    356     private static final int MONOSPACE = 3;
    357 
    358     // Enum for the "ellipsize" XML parameter.
    359     private static final int ELLIPSIZE_NOT_SET = -1;
    360     private static final int ELLIPSIZE_NONE = 0;
    361     private static final int ELLIPSIZE_START = 1;
    362     private static final int ELLIPSIZE_MIDDLE = 2;
    363     private static final int ELLIPSIZE_END = 3;
    364     private static final int ELLIPSIZE_MARQUEE = 4;
    365 
    366     // Bitfield for the "numeric" XML parameter.
    367     // TODO: How can we get this from the XML instead of hardcoding it here?
    368     private static final int SIGNED = 2;
    369     private static final int DECIMAL = 4;
    370 
    371     /**
    372      * Draw marquee text with fading edges as usual
    373      */
    374     private static final int MARQUEE_FADE_NORMAL = 0;
    375 
    376     /**
    377      * Draw marquee text as ellipsize end while inactive instead of with the fade.
    378      * (Useful for devices where the fade can be expensive if overdone)
    379      */
    380     private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
    381 
    382     /**
    383      * Draw marquee text with fading edges because it is currently active/animating.
    384      */
    385     private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
    386 
    387     @UnsupportedAppUsage
    388     private static final int LINES = 1;
    389     private static final int EMS = LINES;
    390     private static final int PIXELS = 2;
    391 
    392     private static final RectF TEMP_RECTF = new RectF();
    393 
    394     /** @hide */
    395     static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
    396     private static final int ANIMATED_SCROLL_GAP = 250;
    397 
    398     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
    399     private static final Spanned EMPTY_SPANNED = new SpannedString("");
    400 
    401     private static final int CHANGE_WATCHER_PRIORITY = 100;
    402 
    403     // New state used to change background based on whether this TextView is multiline.
    404     private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
    405 
    406     // Accessibility action to share selected text.
    407     private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
    408 
    409     /**
    410      * @hide
    411      */
    412     // Accessibility action start id for "process text" actions.
    413     static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
    414 
    415     /**
    416      * @hide
    417      */
    418     static final int PROCESS_TEXT_REQUEST_CODE = 100;
    419 
    420     /**
    421      *  Return code of {@link #doKeyDown}.
    422      */
    423     private static final int KEY_EVENT_NOT_HANDLED = 0;
    424     private static final int KEY_EVENT_HANDLED = -1;
    425     private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
    426     private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
    427 
    428     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
    429 
    430     // System wide time for last cut, copy or text changed action.
    431     static long sLastCutCopyOrTextChangedTime;
    432 
    433     private ColorStateList mTextColor;
    434     private ColorStateList mHintTextColor;
    435     private ColorStateList mLinkTextColor;
    436     @ViewDebug.ExportedProperty(category = "text")
    437 
    438     /**
    439      * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead.
    440      */
    441     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    442     private int mCurTextColor;
    443 
    444     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    445     private int mCurHintTextColor;
    446     private boolean mFreezesText;
    447 
    448     @UnsupportedAppUsage
    449     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
    450     @UnsupportedAppUsage
    451     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
    452 
    453     @UnsupportedAppUsage
    454     private float mShadowRadius;
    455     @UnsupportedAppUsage
    456     private float mShadowDx;
    457     @UnsupportedAppUsage
    458     private float mShadowDy;
    459     private int mShadowColor;
    460 
    461     private boolean mPreDrawRegistered;
    462     private boolean mPreDrawListenerDetached;
    463 
    464     private TextClassifier mTextClassifier;
    465     private TextClassifier mTextClassificationSession;
    466     private TextClassificationContext mTextClassificationContext;
    467 
    468     // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
    469     // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
    470     // the view hierarchy. On the other hand, if the user is using the movement key to traverse
    471     // views (i.e. the first movement was to traverse out of this view, or this view was traversed
    472     // into by the user holding the movement key down) then we shouldn't prevent the focus from
    473     // changing.
    474     private boolean mPreventDefaultMovement;
    475 
    476     private TextUtils.TruncateAt mEllipsize;
    477 
    478     static class Drawables {
    479         static final int LEFT = 0;
    480         static final int TOP = 1;
    481         static final int RIGHT = 2;
    482         static final int BOTTOM = 3;
    483 
    484         static final int DRAWABLE_NONE = -1;
    485         static final int DRAWABLE_RIGHT = 0;
    486         static final int DRAWABLE_LEFT = 1;
    487 
    488         final Rect mCompoundRect = new Rect();
    489 
    490         final Drawable[] mShowing = new Drawable[4];
    491 
    492         ColorStateList mTintList;
    493         BlendMode mBlendMode;
    494         boolean mHasTint;
    495         boolean mHasTintMode;
    496 
    497         Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
    498         Drawable mDrawableLeftInitial, mDrawableRightInitial;
    499 
    500         boolean mIsRtlCompatibilityMode;
    501         boolean mOverride;
    502 
    503         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
    504                 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
    505 
    506         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
    507                 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
    508 
    509         int mDrawablePadding;
    510 
    511         int mDrawableSaved = DRAWABLE_NONE;
    512 
    513         public Drawables(Context context) {
    514             final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    515             mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
    516                     || !context.getApplicationInfo().hasRtlSupport();
    517             mOverride = false;
    518         }
    519 
    520         /**
    521          * @return {@code true} if this object contains metadata that needs to
    522          *         be retained, {@code false} otherwise
    523          */
    524         public boolean hasMetadata() {
    525             return mDrawablePadding != 0 || mHasTintMode || mHasTint;
    526         }
    527 
    528         /**
    529          * Updates the list of displayed drawables to account for the current
    530          * layout direction.
    531          *
    532          * @param layoutDirection the current layout direction
    533          * @return {@code true} if the displayed drawables changed
    534          */
    535         public boolean resolveWithLayoutDirection(int layoutDirection) {
    536             final Drawable previousLeft = mShowing[Drawables.LEFT];
    537             final Drawable previousRight = mShowing[Drawables.RIGHT];
    538 
    539             // First reset "left" and "right" drawables to their initial values
    540             mShowing[Drawables.LEFT] = mDrawableLeftInitial;
    541             mShowing[Drawables.RIGHT] = mDrawableRightInitial;
    542 
    543             if (mIsRtlCompatibilityMode) {
    544                 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
    545                 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
    546                     mShowing[Drawables.LEFT] = mDrawableStart;
    547                     mDrawableSizeLeft = mDrawableSizeStart;
    548                     mDrawableHeightLeft = mDrawableHeightStart;
    549                 }
    550                 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
    551                 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
    552                     mShowing[Drawables.RIGHT] = mDrawableEnd;
    553                     mDrawableSizeRight = mDrawableSizeEnd;
    554                     mDrawableHeightRight = mDrawableHeightEnd;
    555                 }
    556             } else {
    557                 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
    558                 // drawable if and only if they have been defined
    559                 switch(layoutDirection) {
    560                     case LAYOUT_DIRECTION_RTL:
    561                         if (mOverride) {
    562                             mShowing[Drawables.RIGHT] = mDrawableStart;
    563                             mDrawableSizeRight = mDrawableSizeStart;
    564                             mDrawableHeightRight = mDrawableHeightStart;
    565 
    566                             mShowing[Drawables.LEFT] = mDrawableEnd;
    567                             mDrawableSizeLeft = mDrawableSizeEnd;
    568                             mDrawableHeightLeft = mDrawableHeightEnd;
    569                         }
    570                         break;
    571 
    572                     case LAYOUT_DIRECTION_LTR:
    573                     default:
    574                         if (mOverride) {
    575                             mShowing[Drawables.LEFT] = mDrawableStart;
    576                             mDrawableSizeLeft = mDrawableSizeStart;
    577                             mDrawableHeightLeft = mDrawableHeightStart;
    578 
    579                             mShowing[Drawables.RIGHT] = mDrawableEnd;
    580                             mDrawableSizeRight = mDrawableSizeEnd;
    581                             mDrawableHeightRight = mDrawableHeightEnd;
    582                         }
    583                         break;
    584                 }
    585             }
    586 
    587             applyErrorDrawableIfNeeded(layoutDirection);
    588 
    589             return mShowing[Drawables.LEFT] != previousLeft
    590                     || mShowing[Drawables.RIGHT] != previousRight;
    591         }
    592 
    593         public void setErrorDrawable(Drawable dr, TextView tv) {
    594             if (mDrawableError != dr && mDrawableError != null) {
    595                 mDrawableError.setCallback(null);
    596             }
    597             mDrawableError = dr;
    598 
    599             if (mDrawableError != null) {
    600                 final Rect compoundRect = mCompoundRect;
    601                 final int[] state = tv.getDrawableState();
    602 
    603                 mDrawableError.setState(state);
    604                 mDrawableError.copyBounds(compoundRect);
    605                 mDrawableError.setCallback(tv);
    606                 mDrawableSizeError = compoundRect.width();
    607                 mDrawableHeightError = compoundRect.height();
    608             } else {
    609                 mDrawableSizeError = mDrawableHeightError = 0;
    610             }
    611         }
    612 
    613         private void applyErrorDrawableIfNeeded(int layoutDirection) {
    614             // first restore the initial state if needed
    615             switch (mDrawableSaved) {
    616                 case DRAWABLE_LEFT:
    617                     mShowing[Drawables.LEFT] = mDrawableTemp;
    618                     mDrawableSizeLeft = mDrawableSizeTemp;
    619                     mDrawableHeightLeft = mDrawableHeightTemp;
    620                     break;
    621                 case DRAWABLE_RIGHT:
    622                     mShowing[Drawables.RIGHT] = mDrawableTemp;
    623                     mDrawableSizeRight = mDrawableSizeTemp;
    624                     mDrawableHeightRight = mDrawableHeightTemp;
    625                     break;
    626                 case DRAWABLE_NONE:
    627                 default:
    628             }
    629             // then, if needed, assign the Error drawable to the correct location
    630             if (mDrawableError != null) {
    631                 switch(layoutDirection) {
    632                     case LAYOUT_DIRECTION_RTL:
    633                         mDrawableSaved = DRAWABLE_LEFT;
    634 
    635                         mDrawableTemp = mShowing[Drawables.LEFT];
    636                         mDrawableSizeTemp = mDrawableSizeLeft;
    637                         mDrawableHeightTemp = mDrawableHeightLeft;
    638 
    639                         mShowing[Drawables.LEFT] = mDrawableError;
    640                         mDrawableSizeLeft = mDrawableSizeError;
    641                         mDrawableHeightLeft = mDrawableHeightError;
    642                         break;
    643                     case LAYOUT_DIRECTION_LTR:
    644                     default:
    645                         mDrawableSaved = DRAWABLE_RIGHT;
    646 
    647                         mDrawableTemp = mShowing[Drawables.RIGHT];
    648                         mDrawableSizeTemp = mDrawableSizeRight;
    649                         mDrawableHeightTemp = mDrawableHeightRight;
    650 
    651                         mShowing[Drawables.RIGHT] = mDrawableError;
    652                         mDrawableSizeRight = mDrawableSizeError;
    653                         mDrawableHeightRight = mDrawableHeightError;
    654                         break;
    655                 }
    656             }
    657         }
    658     }
    659 
    660     @UnsupportedAppUsage
    661     Drawables mDrawables;
    662 
    663     @UnsupportedAppUsage
    664     private CharWrapper mCharWrapper;
    665 
    666     @UnsupportedAppUsage(trackingBug = 124050217)
    667     private Marquee mMarquee;
    668     @UnsupportedAppUsage
    669     private boolean mRestartMarquee;
    670 
    671     private int mMarqueeRepeatLimit = 3;
    672 
    673     private int mLastLayoutDirection = -1;
    674 
    675     /**
    676      * On some devices the fading edges add a performance penalty if used
    677      * extensively in the same layout. This mode indicates how the marquee
    678      * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
    679      */
    680     @UnsupportedAppUsage
    681     private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
    682 
    683     /**
    684      * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
    685      * the layout that should be used when the mode switches.
    686      */
    687     @UnsupportedAppUsage
    688     private Layout mSavedMarqueeModeLayout;
    689 
    690     // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
    691     @ViewDebug.ExportedProperty(category = "text")
    692     @UnsupportedAppUsage
    693     private @Nullable CharSequence mText;
    694     private @Nullable Spannable mSpannable;
    695     private @Nullable PrecomputedText mPrecomputed;
    696 
    697     @UnsupportedAppUsage
    698     private CharSequence mTransformed;
    699     @UnsupportedAppUsage
    700     private BufferType mBufferType = BufferType.NORMAL;
    701 
    702     private CharSequence mHint;
    703     @UnsupportedAppUsage
    704     private Layout mHintLayout;
    705 
    706     private MovementMethod mMovement;
    707 
    708     private TransformationMethod mTransformation;
    709     @UnsupportedAppUsage
    710     private boolean mAllowTransformationLengthChange;
    711     @UnsupportedAppUsage
    712     private ChangeWatcher mChangeWatcher;
    713 
    714     @UnsupportedAppUsage(trackingBug = 123769451)
    715     private ArrayList<TextWatcher> mListeners;
    716 
    717     // display attributes
    718     @UnsupportedAppUsage
    719     private final TextPaint mTextPaint;
    720     @UnsupportedAppUsage
    721     private boolean mUserSetTextScaleX;
    722     @UnsupportedAppUsage
    723     private Layout mLayout;
    724     private boolean mLocalesChanged = false;
    725 
    726     // True if setKeyListener() has been explicitly called
    727     private boolean mListenerChanged = false;
    728     // True if internationalized input should be used for numbers and date and time.
    729     private final boolean mUseInternationalizedInput;
    730     // True if fallback fonts that end up getting used should be allowed to affect line spacing.
    731     /* package */ boolean mUseFallbackLineSpacing;
    732 
    733     @ViewDebug.ExportedProperty(category = "text")
    734     @UnsupportedAppUsage
    735     private int mGravity = Gravity.TOP | Gravity.START;
    736     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    737     private boolean mHorizontallyScrolling;
    738 
    739     private int mAutoLinkMask;
    740     private boolean mLinksClickable = true;
    741 
    742     @UnsupportedAppUsage
    743     private float mSpacingMult = 1.0f;
    744     @UnsupportedAppUsage
    745     private float mSpacingAdd = 0.0f;
    746 
    747     private int mBreakStrategy;
    748     private int mHyphenationFrequency;
    749     private int mJustificationMode;
    750 
    751     @UnsupportedAppUsage
    752     private int mMaximum = Integer.MAX_VALUE;
    753     @UnsupportedAppUsage
    754     private int mMaxMode = LINES;
    755     @UnsupportedAppUsage
    756     private int mMinimum = 0;
    757     @UnsupportedAppUsage
    758     private int mMinMode = LINES;
    759 
    760     @UnsupportedAppUsage
    761     private int mOldMaximum = mMaximum;
    762     @UnsupportedAppUsage
    763     private int mOldMaxMode = mMaxMode;
    764 
    765     @UnsupportedAppUsage
    766     private int mMaxWidth = Integer.MAX_VALUE;
    767     @UnsupportedAppUsage
    768     private int mMaxWidthMode = PIXELS;
    769     @UnsupportedAppUsage
    770     private int mMinWidth = 0;
    771     @UnsupportedAppUsage
    772     private int mMinWidthMode = PIXELS;
    773 
    774     @UnsupportedAppUsage
    775     private boolean mSingleLine;
    776     @UnsupportedAppUsage
    777     private int mDesiredHeightAtMeasure = -1;
    778     @UnsupportedAppUsage
    779     private boolean mIncludePad = true;
    780     private int mDeferScroll = -1;
    781 
    782     // tmp primitives, so we don't alloc them on each draw
    783     private Rect mTempRect;
    784     private long mLastScroll;
    785     private Scroller mScroller;
    786     private TextPaint mTempTextPaint;
    787 
    788     @UnsupportedAppUsage
    789     private BoringLayout.Metrics mBoring;
    790     @UnsupportedAppUsage
    791     private BoringLayout.Metrics mHintBoring;
    792     @UnsupportedAppUsage
    793     private BoringLayout mSavedLayout;
    794     @UnsupportedAppUsage
    795     private BoringLayout mSavedHintLayout;
    796 
    797     @UnsupportedAppUsage
    798     private TextDirectionHeuristic mTextDir;
    799 
    800     private InputFilter[] mFilters = NO_FILTERS;
    801 
    802     /**
    803      * {@link UserHandle} that represents the logical owner of the text. {@code null} when it is
    804      * the same as {@link Process#myUserHandle()}.
    805      *
    806      * <p>Most of applications should not worry about this. Some privileged apps that host UI for
    807      * other apps may need to set this so that the system can use right user's resources and
    808      * services such as input methods and spell checkers.</p>
    809      *
    810      * @see #setTextOperationUser(UserHandle)
    811      */
    812     @Nullable
    813     private UserHandle mTextOperationUser;
    814 
    815     private volatile Locale mCurrentSpellCheckerLocaleCache;
    816 
    817     // It is possible to have a selection even when mEditor is null (programmatically set, like when
    818     // a link is pressed). These highlight-related fields do not go in mEditor.
    819     @UnsupportedAppUsage
    820     int mHighlightColor = 0x6633B5E5;
    821     private Path mHighlightPath;
    822     @UnsupportedAppUsage
    823     private final Paint mHighlightPaint;
    824     @UnsupportedAppUsage
    825     private boolean mHighlightPathBogus = true;
    826 
    827     // Although these fields are specific to editable text, they are not added to Editor because
    828     // they are defined by the TextView's style and are theme-dependent.
    829     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    830     int mCursorDrawableRes;
    831     private Drawable mCursorDrawable;
    832     // Note: this might be stale if setTextSelectHandleLeft is used. We could simplify the code
    833     // by removing it, but we would break apps targeting <= P that use it by reflection.
    834     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    835     int mTextSelectHandleLeftRes;
    836     private Drawable mTextSelectHandleLeft;
    837     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
    838     // by removing it, but we would break apps targeting <= P that use it by reflection.
    839     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    840     int mTextSelectHandleRightRes;
    841     private Drawable mTextSelectHandleRight;
    842     // Note: this might be stale if setTextSelectHandle is used. We could simplify the code
    843     // by removing it, but we would break apps targeting <= P that use it by reflection.
    844     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    845     int mTextSelectHandleRes;
    846     private Drawable mTextSelectHandle;
    847     int mTextEditSuggestionItemLayout;
    848     int mTextEditSuggestionContainerLayout;
    849     int mTextEditSuggestionHighlightStyle;
    850 
    851     /**
    852      * {@link EditText} specific data, created on demand when one of the Editor fields is used.
    853      * See {@link #createEditorIfNeeded()}.
    854      */
    855     @UnsupportedAppUsage
    856     private Editor mEditor;
    857 
    858     private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
    859     private static final int DEVICE_PROVISIONED_NO = 1;
    860     private static final int DEVICE_PROVISIONED_YES = 2;
    861 
    862     /**
    863      * Some special options such as sharing selected text should only be shown if the device
    864      * is provisioned. Only check the provisioned state once for a given view instance.
    865      */
    866     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
    867 
    868     /**
    869      * The TextView does not auto-size text (default).
    870      */
    871     public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
    872 
    873     /**
    874      * The TextView scales text size both horizontally and vertically to fit within the
    875      * container.
    876      */
    877     public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
    878 
    879     /** @hide */
    880     @IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = {
    881             AUTO_SIZE_TEXT_TYPE_NONE,
    882             AUTO_SIZE_TEXT_TYPE_UNIFORM
    883     })
    884     @Retention(RetentionPolicy.SOURCE)
    885     public @interface AutoSizeTextType {}
    886     // Default minimum size for auto-sizing text in scaled pixels.
    887     private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
    888     // Default maximum size for auto-sizing text in scaled pixels.
    889     private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
    890     // Default value for the step size in pixels.
    891     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
    892     // Use this to specify that any of the auto-size configuration int values have not been set.
    893     private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
    894     // Auto-size text type.
    895     private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
    896     // Specify if auto-size text is needed.
    897     private boolean mNeedsAutoSizeText = false;
    898     // Step size for auto-sizing in pixels.
    899     private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
    900     // Minimum text size for auto-sizing in pixels.
    901     private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
    902     // Maximum text size for auto-sizing in pixels.
    903     private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
    904     // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
    905     // when auto-sizing text.
    906     private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
    907     // Specifies whether auto-size should use the provided auto size steps set or if it should
    908     // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
    909     // mAutoSizeStepGranularityInPx.
    910     private boolean mHasPresetAutoSizeValues = false;
    911 
    912     // Autofill-related attributes
    913     //
    914     // Indicates whether the text was set statically or dynamically, so it can be used to
    915     // sanitize autofill requests.
    916     private boolean mTextSetFromXmlOrResourceId = false;
    917     // Resource id used to set the text.
    918     private @StringRes int mTextId = Resources.ID_NULL;
    919     //
    920     // End of autofill-related attributes
    921 
    922     /**
    923      * Kick-start the font cache for the zygote process (to pay the cost of
    924      * initializing freetype for our default font only once).
    925      * @hide
    926      */
    927     public static void preloadFontCache() {
    928         Paint p = new Paint();
    929         p.setAntiAlias(true);
    930         // Ensure that the Typeface is loaded here.
    931         // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
    932         // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
    933         // since Paint.measureText can not be called without Typeface static initializer.
    934         p.setTypeface(Typeface.DEFAULT);
    935         // We don't care about the result, just the side-effect of measuring.
    936         p.measureText("H");
    937     }
    938 
    939     /**
    940      * Interface definition for a callback to be invoked when an action is
    941      * performed on the editor.
    942      */
    943     public interface OnEditorActionListener {
    944         /**
    945          * Called when an action is being performed.
    946          *
    947          * @param v The view that was clicked.
    948          * @param actionId Identifier of the action.  This will be either the
    949          * identifier you supplied, or {@link EditorInfo#IME_NULL
    950          * EditorInfo.IME_NULL} if being called due to the enter key
    951          * being pressed.
    952          * @param event If triggered by an enter key, this is the event;
    953          * otherwise, this is null.
    954          * @return Return true if you have consumed the action, else false.
    955          */
    956         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
    957     }
    958 
    959     public TextView(Context context) {
    960         this(context, null);
    961     }
    962 
    963     public TextView(Context context, @Nullable AttributeSet attrs) {
    964         this(context, attrs, com.android.internal.R.attr.textViewStyle);
    965     }
    966 
    967     public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    968         this(context, attrs, defStyleAttr, 0);
    969     }
    970 
    971     @SuppressWarnings("deprecation")
    972     public TextView(
    973             Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    974         super(context, attrs, defStyleAttr, defStyleRes);
    975 
    976         // TextView is important by default, unless app developer overrode attribute.
    977         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
    978             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
    979         }
    980 
    981         setTextInternal("");
    982 
    983         final Resources res = getResources();
    984         final CompatibilityInfo compat = res.getCompatibilityInfo();
    985 
    986         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    987         mTextPaint.density = res.getDisplayMetrics().density;
    988         mTextPaint.setCompatibilityScaling(compat.applicationScale);
    989 
    990         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    991         mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
    992 
    993         mMovement = getDefaultMovementMethod();
    994 
    995         mTransformation = null;
    996 
    997         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
    998         attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
    999         attributes.mTextSize = 15;
   1000         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
   1001         mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
   1002         mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
   1003 
   1004         final Resources.Theme theme = context.getTheme();
   1005 
   1006         /*
   1007          * Look the appearance up without checking first if it exists because
   1008          * almost every TextView has one and it greatly simplifies the logic
   1009          * to be able to parse the appearance first and then let specific tags
   1010          * for this View override it.
   1011          */
   1012         TypedArray a = theme.obtainStyledAttributes(attrs,
   1013                 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
   1014         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextViewAppearance,
   1015                 attrs, a, defStyleAttr, defStyleRes);
   1016         TypedArray appearance = null;
   1017         int ap = a.getResourceId(
   1018                 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
   1019         a.recycle();
   1020         if (ap != -1) {
   1021             appearance = theme.obtainStyledAttributes(
   1022                     ap, com.android.internal.R.styleable.TextAppearance);
   1023             saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextAppearance,
   1024                     null, appearance, 0, ap);
   1025         }
   1026         if (appearance != null) {
   1027             readTextAppearance(context, appearance, attributes, false /* styleArray */);
   1028             attributes.mFontFamilyExplicit = false;
   1029             appearance.recycle();
   1030         }
   1031 
   1032         boolean editable = getDefaultEditable();
   1033         CharSequence inputMethod = null;
   1034         int numeric = 0;
   1035         CharSequence digits = null;
   1036         boolean phone = false;
   1037         boolean autotext = false;
   1038         int autocap = -1;
   1039         int buffertype = 0;
   1040         boolean selectallonfocus = false;
   1041         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
   1042                 drawableBottom = null, drawableStart = null, drawableEnd = null;
   1043         ColorStateList drawableTint = null;
   1044         BlendMode drawableTintMode = null;
   1045         int drawablePadding = 0;
   1046         int ellipsize = ELLIPSIZE_NOT_SET;
   1047         boolean singleLine = false;
   1048         int maxlength = -1;
   1049         CharSequence text = "";
   1050         CharSequence hint = null;
   1051         boolean password = false;
   1052         float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   1053         float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   1054         float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   1055         int inputType = EditorInfo.TYPE_NULL;
   1056         a = theme.obtainStyledAttributes(
   1057                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
   1058         saveAttributeDataForStyleable(context, com.android.internal.R.styleable.TextView, attrs, a,
   1059                 defStyleAttr, defStyleRes);
   1060         int firstBaselineToTopHeight = -1;
   1061         int lastBaselineToBottomHeight = -1;
   1062         int lineHeight = -1;
   1063 
   1064         readTextAppearance(context, a, attributes, true /* styleArray */);
   1065 
   1066         int n = a.getIndexCount();
   1067 
   1068         // Must set id in a temporary variable because it will be reset by setText()
   1069         boolean textIsSetFromXml = false;
   1070         for (int i = 0; i < n; i++) {
   1071             int attr = a.getIndex(i);
   1072 
   1073             switch (attr) {
   1074                 case com.android.internal.R.styleable.TextView_editable:
   1075                     editable = a.getBoolean(attr, editable);
   1076                     break;
   1077 
   1078                 case com.android.internal.R.styleable.TextView_inputMethod:
   1079                     inputMethod = a.getText(attr);
   1080                     break;
   1081 
   1082                 case com.android.internal.R.styleable.TextView_numeric:
   1083                     numeric = a.getInt(attr, numeric);
   1084                     break;
   1085 
   1086                 case com.android.internal.R.styleable.TextView_digits:
   1087                     digits = a.getText(attr);
   1088                     break;
   1089 
   1090                 case com.android.internal.R.styleable.TextView_phoneNumber:
   1091                     phone = a.getBoolean(attr, phone);
   1092                     break;
   1093 
   1094                 case com.android.internal.R.styleable.TextView_autoText:
   1095                     autotext = a.getBoolean(attr, autotext);
   1096                     break;
   1097 
   1098                 case com.android.internal.R.styleable.TextView_capitalize:
   1099                     autocap = a.getInt(attr, autocap);
   1100                     break;
   1101 
   1102                 case com.android.internal.R.styleable.TextView_bufferType:
   1103                     buffertype = a.getInt(attr, buffertype);
   1104                     break;
   1105 
   1106                 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
   1107                     selectallonfocus = a.getBoolean(attr, selectallonfocus);
   1108                     break;
   1109 
   1110                 case com.android.internal.R.styleable.TextView_autoLink:
   1111                     mAutoLinkMask = a.getInt(attr, 0);
   1112                     break;
   1113 
   1114                 case com.android.internal.R.styleable.TextView_linksClickable:
   1115                     mLinksClickable = a.getBoolean(attr, true);
   1116                     break;
   1117 
   1118                 case com.android.internal.R.styleable.TextView_drawableLeft:
   1119                     drawableLeft = a.getDrawable(attr);
   1120                     break;
   1121 
   1122                 case com.android.internal.R.styleable.TextView_drawableTop:
   1123                     drawableTop = a.getDrawable(attr);
   1124                     break;
   1125 
   1126                 case com.android.internal.R.styleable.TextView_drawableRight:
   1127                     drawableRight = a.getDrawable(attr);
   1128                     break;
   1129 
   1130                 case com.android.internal.R.styleable.TextView_drawableBottom:
   1131                     drawableBottom = a.getDrawable(attr);
   1132                     break;
   1133 
   1134                 case com.android.internal.R.styleable.TextView_drawableStart:
   1135                     drawableStart = a.getDrawable(attr);
   1136                     break;
   1137 
   1138                 case com.android.internal.R.styleable.TextView_drawableEnd:
   1139                     drawableEnd = a.getDrawable(attr);
   1140                     break;
   1141 
   1142                 case com.android.internal.R.styleable.TextView_drawableTint:
   1143                     drawableTint = a.getColorStateList(attr);
   1144                     break;
   1145 
   1146                 case com.android.internal.R.styleable.TextView_drawableTintMode:
   1147                     drawableTintMode = Drawable.parseBlendMode(a.getInt(attr, -1),
   1148                             drawableTintMode);
   1149                     break;
   1150 
   1151                 case com.android.internal.R.styleable.TextView_drawablePadding:
   1152                     drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
   1153                     break;
   1154 
   1155                 case com.android.internal.R.styleable.TextView_maxLines:
   1156                     setMaxLines(a.getInt(attr, -1));
   1157                     break;
   1158 
   1159                 case com.android.internal.R.styleable.TextView_maxHeight:
   1160                     setMaxHeight(a.getDimensionPixelSize(attr, -1));
   1161                     break;
   1162 
   1163                 case com.android.internal.R.styleable.TextView_lines:
   1164                     setLines(a.getInt(attr, -1));
   1165                     break;
   1166 
   1167                 case com.android.internal.R.styleable.TextView_height:
   1168                     setHeight(a.getDimensionPixelSize(attr, -1));
   1169                     break;
   1170 
   1171                 case com.android.internal.R.styleable.TextView_minLines:
   1172                     setMinLines(a.getInt(attr, -1));
   1173                     break;
   1174 
   1175                 case com.android.internal.R.styleable.TextView_minHeight:
   1176                     setMinHeight(a.getDimensionPixelSize(attr, -1));
   1177                     break;
   1178 
   1179                 case com.android.internal.R.styleable.TextView_maxEms:
   1180                     setMaxEms(a.getInt(attr, -1));
   1181                     break;
   1182 
   1183                 case com.android.internal.R.styleable.TextView_maxWidth:
   1184                     setMaxWidth(a.getDimensionPixelSize(attr, -1));
   1185                     break;
   1186 
   1187                 case com.android.internal.R.styleable.TextView_ems:
   1188                     setEms(a.getInt(attr, -1));
   1189                     break;
   1190 
   1191                 case com.android.internal.R.styleable.TextView_width:
   1192                     setWidth(a.getDimensionPixelSize(attr, -1));
   1193                     break;
   1194 
   1195                 case com.android.internal.R.styleable.TextView_minEms:
   1196                     setMinEms(a.getInt(attr, -1));
   1197                     break;
   1198 
   1199                 case com.android.internal.R.styleable.TextView_minWidth:
   1200                     setMinWidth(a.getDimensionPixelSize(attr, -1));
   1201                     break;
   1202 
   1203                 case com.android.internal.R.styleable.TextView_gravity:
   1204                     setGravity(a.getInt(attr, -1));
   1205                     break;
   1206 
   1207                 case com.android.internal.R.styleable.TextView_hint:
   1208                     hint = a.getText(attr);
   1209                     break;
   1210 
   1211                 case com.android.internal.R.styleable.TextView_text:
   1212                     textIsSetFromXml = true;
   1213                     mTextId = a.getResourceId(attr, Resources.ID_NULL);
   1214                     text = a.getText(attr);
   1215                     break;
   1216 
   1217                 case com.android.internal.R.styleable.TextView_scrollHorizontally:
   1218                     if (a.getBoolean(attr, false)) {
   1219                         setHorizontallyScrolling(true);
   1220                     }
   1221                     break;
   1222 
   1223                 case com.android.internal.R.styleable.TextView_singleLine:
   1224                     singleLine = a.getBoolean(attr, singleLine);
   1225                     break;
   1226 
   1227                 case com.android.internal.R.styleable.TextView_ellipsize:
   1228                     ellipsize = a.getInt(attr, ellipsize);
   1229                     break;
   1230 
   1231                 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
   1232                     setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
   1233                     break;
   1234 
   1235                 case com.android.internal.R.styleable.TextView_includeFontPadding:
   1236                     if (!a.getBoolean(attr, true)) {
   1237                         setIncludeFontPadding(false);
   1238                     }
   1239                     break;
   1240 
   1241                 case com.android.internal.R.styleable.TextView_cursorVisible:
   1242                     if (!a.getBoolean(attr, true)) {
   1243                         setCursorVisible(false);
   1244                     }
   1245                     break;
   1246 
   1247                 case com.android.internal.R.styleable.TextView_maxLength:
   1248                     maxlength = a.getInt(attr, -1);
   1249                     break;
   1250 
   1251                 case com.android.internal.R.styleable.TextView_textScaleX:
   1252                     setTextScaleX(a.getFloat(attr, 1.0f));
   1253                     break;
   1254 
   1255                 case com.android.internal.R.styleable.TextView_freezesText:
   1256                     mFreezesText = a.getBoolean(attr, false);
   1257                     break;
   1258 
   1259                 case com.android.internal.R.styleable.TextView_enabled:
   1260                     setEnabled(a.getBoolean(attr, isEnabled()));
   1261                     break;
   1262 
   1263                 case com.android.internal.R.styleable.TextView_password:
   1264                     password = a.getBoolean(attr, password);
   1265                     break;
   1266 
   1267                 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
   1268                     mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
   1269                     break;
   1270 
   1271                 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
   1272                     mSpacingMult = a.getFloat(attr, mSpacingMult);
   1273                     break;
   1274 
   1275                 case com.android.internal.R.styleable.TextView_inputType:
   1276                     inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
   1277                     break;
   1278 
   1279                 case com.android.internal.R.styleable.TextView_allowUndo:
   1280                     createEditorIfNeeded();
   1281                     mEditor.mAllowUndo = a.getBoolean(attr, true);
   1282                     break;
   1283 
   1284                 case com.android.internal.R.styleable.TextView_imeOptions:
   1285                     createEditorIfNeeded();
   1286                     mEditor.createInputContentTypeIfNeeded();
   1287                     mEditor.mInputContentType.imeOptions = a.getInt(attr,
   1288                             mEditor.mInputContentType.imeOptions);
   1289                     break;
   1290 
   1291                 case com.android.internal.R.styleable.TextView_imeActionLabel:
   1292                     createEditorIfNeeded();
   1293                     mEditor.createInputContentTypeIfNeeded();
   1294                     mEditor.mInputContentType.imeActionLabel = a.getText(attr);
   1295                     break;
   1296 
   1297                 case com.android.internal.R.styleable.TextView_imeActionId:
   1298                     createEditorIfNeeded();
   1299                     mEditor.createInputContentTypeIfNeeded();
   1300                     mEditor.mInputContentType.imeActionId = a.getInt(attr,
   1301                             mEditor.mInputContentType.imeActionId);
   1302                     break;
   1303 
   1304                 case com.android.internal.R.styleable.TextView_privateImeOptions:
   1305                     setPrivateImeOptions(a.getString(attr));
   1306                     break;
   1307 
   1308                 case com.android.internal.R.styleable.TextView_editorExtras:
   1309                     try {
   1310                         setInputExtras(a.getResourceId(attr, 0));
   1311                     } catch (XmlPullParserException e) {
   1312                         Log.w(LOG_TAG, "Failure reading input extras", e);
   1313                     } catch (IOException e) {
   1314                         Log.w(LOG_TAG, "Failure reading input extras", e);
   1315                     }
   1316                     break;
   1317 
   1318                 case com.android.internal.R.styleable.TextView_textCursorDrawable:
   1319                     mCursorDrawableRes = a.getResourceId(attr, 0);
   1320                     break;
   1321 
   1322                 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
   1323                     mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
   1324                     break;
   1325 
   1326                 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
   1327                     mTextSelectHandleRightRes = a.getResourceId(attr, 0);
   1328                     break;
   1329 
   1330                 case com.android.internal.R.styleable.TextView_textSelectHandle:
   1331                     mTextSelectHandleRes = a.getResourceId(attr, 0);
   1332                     break;
   1333 
   1334                 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
   1335                     mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
   1336                     break;
   1337 
   1338                 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
   1339                     mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
   1340                     break;
   1341 
   1342                 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
   1343                     mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
   1344                     break;
   1345 
   1346                 case com.android.internal.R.styleable.TextView_textIsSelectable:
   1347                     setTextIsSelectable(a.getBoolean(attr, false));
   1348                     break;
   1349 
   1350                 case com.android.internal.R.styleable.TextView_breakStrategy:
   1351                     mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
   1352                     break;
   1353 
   1354                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
   1355                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
   1356                     break;
   1357 
   1358                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
   1359                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
   1360                     break;
   1361 
   1362                 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
   1363                     autoSizeStepGranularityInPx = a.getDimension(attr,
   1364                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
   1365                     break;
   1366 
   1367                 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
   1368                     autoSizeMinTextSizeInPx = a.getDimension(attr,
   1369                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
   1370                     break;
   1371 
   1372                 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
   1373                     autoSizeMaxTextSizeInPx = a.getDimension(attr,
   1374                         UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
   1375                     break;
   1376 
   1377                 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
   1378                     final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
   1379                     if (autoSizeStepSizeArrayResId > 0) {
   1380                         final TypedArray autoSizePresetTextSizes = a.getResources()
   1381                                 .obtainTypedArray(autoSizeStepSizeArrayResId);
   1382                         setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
   1383                         autoSizePresetTextSizes.recycle();
   1384                     }
   1385                     break;
   1386                 case com.android.internal.R.styleable.TextView_justificationMode:
   1387                     mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
   1388                     break;
   1389 
   1390                 case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
   1391                     firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
   1392                     break;
   1393 
   1394                 case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
   1395                     lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
   1396                     break;
   1397 
   1398                 case com.android.internal.R.styleable.TextView_lineHeight:
   1399                     lineHeight = a.getDimensionPixelSize(attr, -1);
   1400                     break;
   1401             }
   1402         }
   1403 
   1404         a.recycle();
   1405 
   1406         BufferType bufferType = BufferType.EDITABLE;
   1407 
   1408         final int variation =
   1409                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
   1410         final boolean passwordInputType = variation
   1411                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
   1412         final boolean webPasswordInputType = variation
   1413                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
   1414         final boolean numberPasswordInputType = variation
   1415                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
   1416 
   1417         final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
   1418         mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
   1419         mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
   1420 
   1421         if (inputMethod != null) {
   1422             Class<?> c;
   1423 
   1424             try {
   1425                 c = Class.forName(inputMethod.toString());
   1426             } catch (ClassNotFoundException ex) {
   1427                 throw new RuntimeException(ex);
   1428             }
   1429 
   1430             try {
   1431                 createEditorIfNeeded();
   1432                 mEditor.mKeyListener = (KeyListener) c.newInstance();
   1433             } catch (InstantiationException ex) {
   1434                 throw new RuntimeException(ex);
   1435             } catch (IllegalAccessException ex) {
   1436                 throw new RuntimeException(ex);
   1437             }
   1438             try {
   1439                 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
   1440                         ? inputType
   1441                         : mEditor.mKeyListener.getInputType();
   1442             } catch (IncompatibleClassChangeError e) {
   1443                 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
   1444             }
   1445         } else if (digits != null) {
   1446             createEditorIfNeeded();
   1447             mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
   1448             // If no input type was specified, we will default to generic
   1449             // text, since we can't tell the IME about the set of digits
   1450             // that was selected.
   1451             mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
   1452                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
   1453         } else if (inputType != EditorInfo.TYPE_NULL) {
   1454             setInputType(inputType, true);
   1455             // If set, the input type overrides what was set using the deprecated singleLine flag.
   1456             singleLine = !isMultilineInputType(inputType);
   1457         } else if (phone) {
   1458             createEditorIfNeeded();
   1459             mEditor.mKeyListener = DialerKeyListener.getInstance();
   1460             mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
   1461         } else if (numeric != 0) {
   1462             createEditorIfNeeded();
   1463             mEditor.mKeyListener = DigitsKeyListener.getInstance(
   1464                     null,  // locale
   1465                     (numeric & SIGNED) != 0,
   1466                     (numeric & DECIMAL) != 0);
   1467             inputType = mEditor.mKeyListener.getInputType();
   1468             mEditor.mInputType = inputType;
   1469         } else if (autotext || autocap != -1) {
   1470             TextKeyListener.Capitalize cap;
   1471 
   1472             inputType = EditorInfo.TYPE_CLASS_TEXT;
   1473 
   1474             switch (autocap) {
   1475                 case 1:
   1476                     cap = TextKeyListener.Capitalize.SENTENCES;
   1477                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
   1478                     break;
   1479 
   1480                 case 2:
   1481                     cap = TextKeyListener.Capitalize.WORDS;
   1482                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
   1483                     break;
   1484 
   1485                 case 3:
   1486                     cap = TextKeyListener.Capitalize.CHARACTERS;
   1487                     inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
   1488                     break;
   1489 
   1490                 default:
   1491                     cap = TextKeyListener.Capitalize.NONE;
   1492                     break;
   1493             }
   1494 
   1495             createEditorIfNeeded();
   1496             mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
   1497             mEditor.mInputType = inputType;
   1498         } else if (editable) {
   1499             createEditorIfNeeded();
   1500             mEditor.mKeyListener = TextKeyListener.getInstance();
   1501             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
   1502         } else if (isTextSelectable()) {
   1503             // Prevent text changes from keyboard.
   1504             if (mEditor != null) {
   1505                 mEditor.mKeyListener = null;
   1506                 mEditor.mInputType = EditorInfo.TYPE_NULL;
   1507             }
   1508             bufferType = BufferType.SPANNABLE;
   1509             // So that selection can be changed using arrow keys and touch is handled.
   1510             setMovementMethod(ArrowKeyMovementMethod.getInstance());
   1511         } else {
   1512             if (mEditor != null) mEditor.mKeyListener = null;
   1513 
   1514             switch (buffertype) {
   1515                 case 0:
   1516                     bufferType = BufferType.NORMAL;
   1517                     break;
   1518                 case 1:
   1519                     bufferType = BufferType.SPANNABLE;
   1520                     break;
   1521                 case 2:
   1522                     bufferType = BufferType.EDITABLE;
   1523                     break;
   1524             }
   1525         }
   1526 
   1527         if (mEditor != null) {
   1528             mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
   1529                     numberPasswordInputType);
   1530         }
   1531 
   1532         if (selectallonfocus) {
   1533             createEditorIfNeeded();
   1534             mEditor.mSelectAllOnFocus = true;
   1535 
   1536             if (bufferType == BufferType.NORMAL) {
   1537                 bufferType = BufferType.SPANNABLE;
   1538             }
   1539         }
   1540 
   1541         // Set up the tint (if needed) before setting the drawables so that it
   1542         // gets applied correctly.
   1543         if (drawableTint != null || drawableTintMode != null) {
   1544             if (mDrawables == null) {
   1545                 mDrawables = new Drawables(context);
   1546             }
   1547             if (drawableTint != null) {
   1548                 mDrawables.mTintList = drawableTint;
   1549                 mDrawables.mHasTint = true;
   1550             }
   1551             if (drawableTintMode != null) {
   1552                 mDrawables.mBlendMode = drawableTintMode;
   1553                 mDrawables.mHasTintMode = true;
   1554             }
   1555         }
   1556 
   1557         // This call will save the initial left/right drawables
   1558         setCompoundDrawablesWithIntrinsicBounds(
   1559                 drawableLeft, drawableTop, drawableRight, drawableBottom);
   1560         setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
   1561         setCompoundDrawablePadding(drawablePadding);
   1562 
   1563         // Same as setSingleLine(), but make sure the transformation method and the maximum number
   1564         // of lines of height are unchanged for multi-line TextViews.
   1565         setInputTypeSingleLine(singleLine);
   1566         applySingleLine(singleLine, singleLine, singleLine);
   1567 
   1568         if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
   1569             ellipsize = ELLIPSIZE_END;
   1570         }
   1571 
   1572         switch (ellipsize) {
   1573             case ELLIPSIZE_START:
   1574                 setEllipsize(TextUtils.TruncateAt.START);
   1575                 break;
   1576             case ELLIPSIZE_MIDDLE:
   1577                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
   1578                 break;
   1579             case ELLIPSIZE_END:
   1580                 setEllipsize(TextUtils.TruncateAt.END);
   1581                 break;
   1582             case ELLIPSIZE_MARQUEE:
   1583                 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
   1584                     setHorizontalFadingEdgeEnabled(true);
   1585                     mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
   1586                 } else {
   1587                     setHorizontalFadingEdgeEnabled(false);
   1588                     mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   1589                 }
   1590                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
   1591                 break;
   1592         }
   1593 
   1594         final boolean isPassword = password || passwordInputType || webPasswordInputType
   1595                 || numberPasswordInputType;
   1596         final boolean isMonospaceEnforced = isPassword || (mEditor != null
   1597                 && (mEditor.mInputType
   1598                 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
   1599                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
   1600         if (isMonospaceEnforced) {
   1601             attributes.mTypefaceIndex = MONOSPACE;
   1602         }
   1603 
   1604         applyTextAppearance(attributes);
   1605 
   1606         if (isPassword) {
   1607             setTransformationMethod(PasswordTransformationMethod.getInstance());
   1608         }
   1609 
   1610         if (maxlength >= 0) {
   1611             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
   1612         } else {
   1613             setFilters(NO_FILTERS);
   1614         }
   1615 
   1616         setText(text, bufferType);
   1617         if (mText == null) {
   1618             mText = "";
   1619         }
   1620         if (mTransformed == null) {
   1621             mTransformed = "";
   1622         }
   1623 
   1624         if (textIsSetFromXml) {
   1625             mTextSetFromXmlOrResourceId = true;
   1626         }
   1627 
   1628         if (hint != null) setHint(hint);
   1629 
   1630         /*
   1631          * Views are not normally clickable unless specified to be.
   1632          * However, TextViews that have input or movement methods *are*
   1633          * clickable by default. By setting clickable here, we implicitly set focusable as well
   1634          * if not overridden by the developer.
   1635          */
   1636         a = context.obtainStyledAttributes(
   1637                 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
   1638         boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
   1639         boolean clickable = canInputOrMove || isClickable();
   1640         boolean longClickable = canInputOrMove || isLongClickable();
   1641         int focusable = getFocusable();
   1642 
   1643         n = a.getIndexCount();
   1644         for (int i = 0; i < n; i++) {
   1645             int attr = a.getIndex(i);
   1646 
   1647             switch (attr) {
   1648                 case com.android.internal.R.styleable.View_focusable:
   1649                     TypedValue val = new TypedValue();
   1650                     if (a.getValue(attr, val)) {
   1651                         focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
   1652                                 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
   1653                                 : val.data;
   1654                     }
   1655                     break;
   1656 
   1657                 case com.android.internal.R.styleable.View_clickable:
   1658                     clickable = a.getBoolean(attr, clickable);
   1659                     break;
   1660 
   1661                 case com.android.internal.R.styleable.View_longClickable:
   1662                     longClickable = a.getBoolean(attr, longClickable);
   1663                     break;
   1664             }
   1665         }
   1666         a.recycle();
   1667 
   1668         // Some apps were relying on the undefined behavior of focusable winning over
   1669         // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
   1670         // when starting with EditText and setting only focusable=false). To keep those apps from
   1671         // breaking, re-apply the focusable attribute here.
   1672         if (focusable != getFocusable()) {
   1673             setFocusable(focusable);
   1674         }
   1675         setClickable(clickable);
   1676         setLongClickable(longClickable);
   1677 
   1678         if (mEditor != null) mEditor.prepareCursorControllers();
   1679 
   1680         // If not explicitly specified this view is important for accessibility.
   1681         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
   1682             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
   1683         }
   1684 
   1685         if (supportsAutoSizeText()) {
   1686             if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
   1687                 // If uniform auto-size has been specified but preset values have not been set then
   1688                 // replace the auto-size configuration values that have not been specified with the
   1689                 // defaults.
   1690                 if (!mHasPresetAutoSizeValues) {
   1691                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
   1692 
   1693                     if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
   1694                         autoSizeMinTextSizeInPx = TypedValue.applyDimension(
   1695                                 TypedValue.COMPLEX_UNIT_SP,
   1696                                 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
   1697                                 displayMetrics);
   1698                     }
   1699 
   1700                     if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
   1701                         autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
   1702                                 TypedValue.COMPLEX_UNIT_SP,
   1703                                 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
   1704                                 displayMetrics);
   1705                     }
   1706 
   1707                     if (autoSizeStepGranularityInPx
   1708                             == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
   1709                         autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
   1710                     }
   1711 
   1712                     validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
   1713                             autoSizeMaxTextSizeInPx,
   1714                             autoSizeStepGranularityInPx);
   1715                 }
   1716 
   1717                 setupAutoSizeText();
   1718             }
   1719         } else {
   1720             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
   1721         }
   1722 
   1723         if (firstBaselineToTopHeight >= 0) {
   1724             setFirstBaselineToTopHeight(firstBaselineToTopHeight);
   1725         }
   1726         if (lastBaselineToBottomHeight >= 0) {
   1727             setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
   1728         }
   1729         if (lineHeight >= 0) {
   1730             setLineHeight(lineHeight);
   1731         }
   1732     }
   1733 
   1734     // Update mText and mPrecomputed
   1735     private void setTextInternal(@Nullable CharSequence text) {
   1736         mText = text;
   1737         mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
   1738         mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
   1739     }
   1740 
   1741     /**
   1742      * Specify whether this widget should automatically scale the text to try to perfectly fit
   1743      * within the layout bounds by using the default auto-size configuration.
   1744      *
   1745      * @param autoSizeTextType the type of auto-size. Must be one of
   1746      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
   1747      *        {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
   1748      *
   1749      * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
   1750      *
   1751      * @attr ref android.R.styleable#TextView_autoSizeTextType
   1752      *
   1753      * @see #getAutoSizeTextType()
   1754      */
   1755     public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
   1756         if (supportsAutoSizeText()) {
   1757             switch (autoSizeTextType) {
   1758                 case AUTO_SIZE_TEXT_TYPE_NONE:
   1759                     clearAutoSizeConfiguration();
   1760                     break;
   1761                 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
   1762                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
   1763                     final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
   1764                             TypedValue.COMPLEX_UNIT_SP,
   1765                             DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
   1766                             displayMetrics);
   1767                     final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
   1768                             TypedValue.COMPLEX_UNIT_SP,
   1769                             DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
   1770                             displayMetrics);
   1771 
   1772                     validateAndSetAutoSizeTextTypeUniformConfiguration(
   1773                             autoSizeMinTextSizeInPx,
   1774                             autoSizeMaxTextSizeInPx,
   1775                             DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
   1776                     if (setupAutoSizeText()) {
   1777                         autoSizeText();
   1778                         invalidate();
   1779                     }
   1780                     break;
   1781                 default:
   1782                     throw new IllegalArgumentException(
   1783                             "Unknown auto-size text type: " + autoSizeTextType);
   1784             }
   1785         }
   1786     }
   1787 
   1788     /**
   1789      * Specify whether this widget should automatically scale the text to try to perfectly fit
   1790      * within the layout bounds. If all the configuration params are valid the type of auto-size is
   1791      * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
   1792      *
   1793      * @param autoSizeMinTextSize the minimum text size available for auto-size
   1794      * @param autoSizeMaxTextSize the maximum text size available for auto-size
   1795      * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
   1796      *                                the minimum and maximum text size in order to build the set of
   1797      *                                text sizes the system uses to choose from when auto-sizing
   1798      * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
   1799      *             possible dimension units
   1800      *
   1801      * @throws IllegalArgumentException if any of the configuration params are invalid.
   1802      *
   1803      * @attr ref android.R.styleable#TextView_autoSizeTextType
   1804      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
   1805      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
   1806      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
   1807      *
   1808      * @see #setAutoSizeTextTypeWithDefaults(int)
   1809      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
   1810      * @see #getAutoSizeMinTextSize()
   1811      * @see #getAutoSizeMaxTextSize()
   1812      * @see #getAutoSizeStepGranularity()
   1813      * @see #getAutoSizeTextAvailableSizes()
   1814      */
   1815     public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
   1816             int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
   1817         if (supportsAutoSizeText()) {
   1818             final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
   1819             final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
   1820                     unit, autoSizeMinTextSize, displayMetrics);
   1821             final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
   1822                     unit, autoSizeMaxTextSize, displayMetrics);
   1823             final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
   1824                     unit, autoSizeStepGranularity, displayMetrics);
   1825 
   1826             validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
   1827                     autoSizeMaxTextSizeInPx,
   1828                     autoSizeStepGranularityInPx);
   1829 
   1830             if (setupAutoSizeText()) {
   1831                 autoSizeText();
   1832                 invalidate();
   1833             }
   1834         }
   1835     }
   1836 
   1837     /**
   1838      * Specify whether this widget should automatically scale the text to try to perfectly fit
   1839      * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
   1840      * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
   1841      *
   1842      * @param presetSizes an {@code int} array of sizes in pixels
   1843      * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
   1844      *             the possible dimension units
   1845      *
   1846      * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
   1847      *
   1848      * @attr ref android.R.styleable#TextView_autoSizeTextType
   1849      * @attr ref android.R.styleable#TextView_autoSizePresetSizes
   1850      *
   1851      * @see #setAutoSizeTextTypeWithDefaults(int)
   1852      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1853      * @see #getAutoSizeMinTextSize()
   1854      * @see #getAutoSizeMaxTextSize()
   1855      * @see #getAutoSizeTextAvailableSizes()
   1856      */
   1857     public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
   1858         if (supportsAutoSizeText()) {
   1859             final int presetSizesLength = presetSizes.length;
   1860             if (presetSizesLength > 0) {
   1861                 int[] presetSizesInPx = new int[presetSizesLength];
   1862 
   1863                 if (unit == TypedValue.COMPLEX_UNIT_PX) {
   1864                     presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
   1865                 } else {
   1866                     final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
   1867                     // Convert all to sizes to pixels.
   1868                     for (int i = 0; i < presetSizesLength; i++) {
   1869                         presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
   1870                             presetSizes[i], displayMetrics));
   1871                     }
   1872                 }
   1873 
   1874                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
   1875                 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
   1876                     throw new IllegalArgumentException("None of the preset sizes is valid: "
   1877                             + Arrays.toString(presetSizes));
   1878                 }
   1879             } else {
   1880                 mHasPresetAutoSizeValues = false;
   1881             }
   1882 
   1883             if (setupAutoSizeText()) {
   1884                 autoSizeText();
   1885                 invalidate();
   1886             }
   1887         }
   1888     }
   1889 
   1890     /**
   1891      * Returns the type of auto-size set for this widget.
   1892      *
   1893      * @return an {@code int} corresponding to one of the auto-size types:
   1894      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
   1895      *         {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
   1896      *
   1897      * @attr ref android.R.styleable#TextView_autoSizeTextType
   1898      *
   1899      * @see #setAutoSizeTextTypeWithDefaults(int)
   1900      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1901      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
   1902      */
   1903     @InspectableProperty(enumMapping = {
   1904             @EnumEntry(name = "none", value = AUTO_SIZE_TEXT_TYPE_NONE),
   1905             @EnumEntry(name = "uniform", value = AUTO_SIZE_TEXT_TYPE_UNIFORM)
   1906     })
   1907     @AutoSizeTextType
   1908     public int getAutoSizeTextType() {
   1909         return mAutoSizeTextType;
   1910     }
   1911 
   1912     /**
   1913      * @return the current auto-size step granularity in pixels.
   1914      *
   1915      * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
   1916      *
   1917      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1918      */
   1919     @InspectableProperty
   1920     public int getAutoSizeStepGranularity() {
   1921         return Math.round(mAutoSizeStepGranularityInPx);
   1922     }
   1923 
   1924     /**
   1925      * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
   1926      *         if auto-size has not been configured this function returns {@code -1}.
   1927      *
   1928      * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
   1929      *
   1930      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1931      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
   1932      */
   1933     @InspectableProperty
   1934     public int getAutoSizeMinTextSize() {
   1935         return Math.round(mAutoSizeMinTextSizeInPx);
   1936     }
   1937 
   1938     /**
   1939      * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
   1940      *         if auto-size has not been configured this function returns {@code -1}.
   1941      *
   1942      * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
   1943      *
   1944      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1945      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
   1946      */
   1947     @InspectableProperty
   1948     public int getAutoSizeMaxTextSize() {
   1949         return Math.round(mAutoSizeMaxTextSizeInPx);
   1950     }
   1951 
   1952     /**
   1953      * @return the current auto-size {@code int} sizes array (in pixels).
   1954      *
   1955      * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
   1956      * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
   1957      */
   1958     public int[] getAutoSizeTextAvailableSizes() {
   1959         return mAutoSizeTextSizesInPx;
   1960     }
   1961 
   1962     private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
   1963         final int textSizesLength = textSizes.length();
   1964         final int[] parsedSizes = new int[textSizesLength];
   1965 
   1966         if (textSizesLength > 0) {
   1967             for (int i = 0; i < textSizesLength; i++) {
   1968                 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
   1969             }
   1970             mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
   1971             setupAutoSizeUniformPresetSizesConfiguration();
   1972         }
   1973     }
   1974 
   1975     private boolean setupAutoSizeUniformPresetSizesConfiguration() {
   1976         final int sizesLength = mAutoSizeTextSizesInPx.length;
   1977         mHasPresetAutoSizeValues = sizesLength > 0;
   1978         if (mHasPresetAutoSizeValues) {
   1979             mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
   1980             mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
   1981             mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
   1982             mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   1983         }
   1984         return mHasPresetAutoSizeValues;
   1985     }
   1986 
   1987     /**
   1988      * If all params are valid then save the auto-size configuration.
   1989      *
   1990      * @throws IllegalArgumentException if any of the params are invalid
   1991      */
   1992     private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
   1993             float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
   1994         // First validate.
   1995         if (autoSizeMinTextSizeInPx <= 0) {
   1996             throw new IllegalArgumentException("Minimum auto-size text size ("
   1997                 + autoSizeMinTextSizeInPx  + "px) is less or equal to (0px)");
   1998         }
   1999 
   2000         if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
   2001             throw new IllegalArgumentException("Maximum auto-size text size ("
   2002                 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
   2003                 + "text size (" + autoSizeMinTextSizeInPx + "px)");
   2004         }
   2005 
   2006         if (autoSizeStepGranularityInPx <= 0) {
   2007             throw new IllegalArgumentException("The auto-size step granularity ("
   2008                 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
   2009         }
   2010 
   2011         // All good, persist the configuration.
   2012         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
   2013         mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
   2014         mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
   2015         mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
   2016         mHasPresetAutoSizeValues = false;
   2017     }
   2018 
   2019     private void clearAutoSizeConfiguration() {
   2020         mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
   2021         mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   2022         mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   2023         mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
   2024         mAutoSizeTextSizesInPx = EmptyArray.INT;
   2025         mNeedsAutoSizeText = false;
   2026     }
   2027 
   2028     // Returns distinct sorted positive values.
   2029     private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
   2030         final int presetValuesLength = presetValues.length;
   2031         if (presetValuesLength == 0) {
   2032             return presetValues;
   2033         }
   2034         Arrays.sort(presetValues);
   2035 
   2036         final IntArray uniqueValidSizes = new IntArray();
   2037         for (int i = 0; i < presetValuesLength; i++) {
   2038             final int currentPresetValue = presetValues[i];
   2039 
   2040             if (currentPresetValue > 0
   2041                     && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
   2042                 uniqueValidSizes.add(currentPresetValue);
   2043             }
   2044         }
   2045 
   2046         return presetValuesLength == uniqueValidSizes.size()
   2047             ? presetValues
   2048             : uniqueValidSizes.toArray();
   2049     }
   2050 
   2051     private boolean setupAutoSizeText() {
   2052         if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
   2053             // Calculate the sizes set based on minimum size, maximum size and step size if we do
   2054             // not have a predefined set of sizes or if the current sizes array is empty.
   2055             if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
   2056                 final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx
   2057                         - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1;
   2058                 final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
   2059                 for (int i = 0; i < autoSizeValuesLength; i++) {
   2060                     autoSizeTextSizesInPx[i] = Math.round(
   2061                             mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx));
   2062                 }
   2063                 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
   2064             }
   2065 
   2066             mNeedsAutoSizeText = true;
   2067         } else {
   2068             mNeedsAutoSizeText = false;
   2069         }
   2070 
   2071         return mNeedsAutoSizeText;
   2072     }
   2073 
   2074     private int[] parseDimensionArray(TypedArray dimens) {
   2075         if (dimens == null) {
   2076             return null;
   2077         }
   2078         int[] result = new int[dimens.length()];
   2079         for (int i = 0; i < result.length; i++) {
   2080             result[i] = dimens.getDimensionPixelSize(i, 0);
   2081         }
   2082         return result;
   2083     }
   2084 
   2085     /**
   2086      * @hide
   2087      */
   2088     @Override
   2089     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   2090         if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
   2091             if (resultCode == Activity.RESULT_OK && data != null) {
   2092                 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
   2093                 if (result != null) {
   2094                     if (isTextEditable()) {
   2095                         replaceSelectionWithText(result);
   2096                         if (mEditor != null) {
   2097                             mEditor.refreshTextActionMode();
   2098                         }
   2099                     } else {
   2100                         if (result.length() > 0) {
   2101                             Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
   2102                                 .show();
   2103                         }
   2104                     }
   2105                 }
   2106             } else if (mSpannable != null) {
   2107                 // Reset the selection.
   2108                 Selection.setSelection(mSpannable, getSelectionEnd());
   2109             }
   2110         }
   2111     }
   2112 
   2113     /**
   2114      * Sets the Typeface taking into account the given attributes.
   2115      *
   2116      * @param typeface a typeface
   2117      * @param familyName family name string, e.g. "serif"
   2118      * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF.
   2119      * @param style a typeface style
   2120      * @param weight a weight value for the Typeface or -1 if not specified.
   2121      */
   2122     private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName,
   2123             @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style,
   2124             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
   2125         if (typeface == null && familyName != null) {
   2126             // Lookup normal Typeface from system font map.
   2127             final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL);
   2128             resolveStyleAndSetTypeface(normalTypeface, style, weight);
   2129         } else if (typeface != null) {
   2130             resolveStyleAndSetTypeface(typeface, style, weight);
   2131         } else {  // both typeface and familyName is null.
   2132             switch (typefaceIndex) {
   2133                 case SANS:
   2134                     resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight);
   2135                     break;
   2136                 case SERIF:
   2137                     resolveStyleAndSetTypeface(Typeface.SERIF, style, weight);
   2138                     break;
   2139                 case MONOSPACE:
   2140                     resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight);
   2141                     break;
   2142                 case DEFAULT_TYPEFACE:
   2143                 default:
   2144                     resolveStyleAndSetTypeface(null, style, weight);
   2145                     break;
   2146             }
   2147         }
   2148     }
   2149 
   2150     private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style,
   2151             @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int weight) {
   2152         if (weight >= 0) {
   2153             weight = Math.min(FontStyle.FONT_WEIGHT_MAX, weight);
   2154             final boolean italic = (style & Typeface.ITALIC) != 0;
   2155             setTypeface(Typeface.create(typeface, weight, italic));
   2156         } else {
   2157             setTypeface(typeface, style);
   2158         }
   2159     }
   2160 
   2161     private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
   2162         boolean hasRelativeDrawables = (start != null) || (end != null);
   2163         if (hasRelativeDrawables) {
   2164             Drawables dr = mDrawables;
   2165             if (dr == null) {
   2166                 mDrawables = dr = new Drawables(getContext());
   2167             }
   2168             mDrawables.mOverride = true;
   2169             final Rect compoundRect = dr.mCompoundRect;
   2170             int[] state = getDrawableState();
   2171             if (start != null) {
   2172                 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
   2173                 start.setState(state);
   2174                 start.copyBounds(compoundRect);
   2175                 start.setCallback(this);
   2176 
   2177                 dr.mDrawableStart = start;
   2178                 dr.mDrawableSizeStart = compoundRect.width();
   2179                 dr.mDrawableHeightStart = compoundRect.height();
   2180             } else {
   2181                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
   2182             }
   2183             if (end != null) {
   2184                 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
   2185                 end.setState(state);
   2186                 end.copyBounds(compoundRect);
   2187                 end.setCallback(this);
   2188 
   2189                 dr.mDrawableEnd = end;
   2190                 dr.mDrawableSizeEnd = compoundRect.width();
   2191                 dr.mDrawableHeightEnd = compoundRect.height();
   2192             } else {
   2193                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
   2194             }
   2195             resetResolvedDrawables();
   2196             resolveDrawables();
   2197             applyCompoundDrawableTint();
   2198         }
   2199     }
   2200 
   2201     @android.view.RemotableViewMethod
   2202     @Override
   2203     public void setEnabled(boolean enabled) {
   2204         if (enabled == isEnabled()) {
   2205             return;
   2206         }
   2207 
   2208         if (!enabled) {
   2209             // Hide the soft input if the currently active TextView is disabled
   2210             InputMethodManager imm = getInputMethodManager();
   2211             if (imm != null && imm.isActive(this)) {
   2212                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
   2213             }
   2214         }
   2215 
   2216         super.setEnabled(enabled);
   2217 
   2218         if (enabled) {
   2219             // Make sure IME is updated with current editor info.
   2220             InputMethodManager imm = getInputMethodManager();
   2221             if (imm != null) imm.restartInput(this);
   2222         }
   2223 
   2224         // Will change text color
   2225         if (mEditor != null) {
   2226             mEditor.invalidateTextDisplayList();
   2227             mEditor.prepareCursorControllers();
   2228 
   2229             // start or stop the cursor blinking as appropriate
   2230             mEditor.makeBlink();
   2231         }
   2232     }
   2233 
   2234     /**
   2235      * Sets the typeface and style in which the text should be displayed,
   2236      * and turns on the fake bold and italic bits in the Paint if the
   2237      * Typeface that you provided does not have all the bits in the
   2238      * style that you specified.
   2239      *
   2240      * @attr ref android.R.styleable#TextView_typeface
   2241      * @attr ref android.R.styleable#TextView_textStyle
   2242      */
   2243     public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) {
   2244         if (style > 0) {
   2245             if (tf == null) {
   2246                 tf = Typeface.defaultFromStyle(style);
   2247             } else {
   2248                 tf = Typeface.create(tf, style);
   2249             }
   2250 
   2251             setTypeface(tf);
   2252             // now compute what (if any) algorithmic styling is needed
   2253             int typefaceStyle = tf != null ? tf.getStyle() : 0;
   2254             int need = style & ~typefaceStyle;
   2255             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
   2256             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
   2257         } else {
   2258             mTextPaint.setFakeBoldText(false);
   2259             mTextPaint.setTextSkewX(0);
   2260             setTypeface(tf);
   2261         }
   2262     }
   2263 
   2264     /**
   2265      * Subclasses override this to specify that they have a KeyListener
   2266      * by default even if not specifically called for in the XML options.
   2267      */
   2268     protected boolean getDefaultEditable() {
   2269         return false;
   2270     }
   2271 
   2272     /**
   2273      * Subclasses override this to specify a default movement method.
   2274      */
   2275     protected MovementMethod getDefaultMovementMethod() {
   2276         return null;
   2277     }
   2278 
   2279     /**
   2280      * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
   2281      * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
   2282      * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
   2283      * the return value from this method to Spannable or Editable, respectively.
   2284      *
   2285      * <p>The content of the return value should not be modified. If you want a modifiable one, you
   2286      * should make your own copy first.</p>
   2287      *
   2288      * @return The text displayed by the text view.
   2289      * @attr ref android.R.styleable#TextView_text
   2290      */
   2291     @ViewDebug.CapturedViewProperty
   2292     @InspectableProperty
   2293     public CharSequence getText() {
   2294         return mText;
   2295     }
   2296 
   2297     /**
   2298      * Returns the length, in characters, of the text managed by this TextView
   2299      * @return The length of the text managed by the TextView in characters.
   2300      */
   2301     public int length() {
   2302         return mText.length();
   2303     }
   2304 
   2305     /**
   2306      * Return the text that TextView is displaying as an Editable object. If the text is not
   2307      * editable, null is returned.
   2308      *
   2309      * @see #getText
   2310      */
   2311     public Editable getEditableText() {
   2312         return (mText instanceof Editable) ? (Editable) mText : null;
   2313     }
   2314 
   2315     /**
   2316      * @hide
   2317      */
   2318     @VisibleForTesting
   2319     public CharSequence getTransformed() {
   2320         return mTransformed;
   2321     }
   2322 
   2323     /**
   2324      * Gets the vertical distance between lines of text, in pixels.
   2325      * Note that markup within the text can cause individual lines
   2326      * to be taller or shorter than this height, and the layout may
   2327      * contain additional first-or last-line padding.
   2328      * @return The height of one standard line in pixels.
   2329      */
   2330     @InspectableProperty
   2331     public int getLineHeight() {
   2332         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
   2333     }
   2334 
   2335     /**
   2336      * Gets the {@link android.text.Layout} that is currently being used to display the text.
   2337      * This value can be null if the text or width has recently changed.
   2338      * @return The Layout that is currently being used to display the text.
   2339      */
   2340     public final Layout getLayout() {
   2341         return mLayout;
   2342     }
   2343 
   2344     /**
   2345      * @return the {@link android.text.Layout} that is currently being used to
   2346      * display the hint text. This can be null.
   2347      */
   2348     @UnsupportedAppUsage
   2349     final Layout getHintLayout() {
   2350         return mHintLayout;
   2351     }
   2352 
   2353     /**
   2354      * Retrieve the {@link android.content.UndoManager} that is currently associated
   2355      * with this TextView.  By default there is no associated UndoManager, so null
   2356      * is returned.  One can be associated with the TextView through
   2357      * {@link #setUndoManager(android.content.UndoManager, String)}
   2358      *
   2359      * @hide
   2360      */
   2361     public final UndoManager getUndoManager() {
   2362         // TODO: Consider supporting a global undo manager.
   2363         throw new UnsupportedOperationException("not implemented");
   2364     }
   2365 
   2366 
   2367     /**
   2368      * @hide
   2369      */
   2370     @VisibleForTesting
   2371     public final Editor getEditorForTesting() {
   2372         return mEditor;
   2373     }
   2374 
   2375     /**
   2376      * Associate an {@link android.content.UndoManager} with this TextView.  Once
   2377      * done, all edit operations on the TextView will result in appropriate
   2378      * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
   2379      * stack.
   2380      *
   2381      * @param undoManager The {@link android.content.UndoManager} to associate with
   2382      * this TextView, or null to clear any existing association.
   2383      * @param tag String tag identifying this particular TextView owner in the
   2384      * UndoManager.  This is used to keep the correct association with the
   2385      * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
   2386      *
   2387      * @hide
   2388      */
   2389     public final void setUndoManager(UndoManager undoManager, String tag) {
   2390         // TODO: Consider supporting a global undo manager. An implementation will need to:
   2391         // * createEditorIfNeeded()
   2392         // * Promote to BufferType.EDITABLE if needed.
   2393         // * Update the UndoManager and UndoOwner.
   2394         // Likewise it will need to be able to restore the default UndoManager.
   2395         throw new UnsupportedOperationException("not implemented");
   2396     }
   2397 
   2398     /**
   2399      * Gets the current {@link KeyListener} for the TextView.
   2400      * This will frequently be null for non-EditText TextViews.
   2401      * @return the current key listener for this TextView.
   2402      *
   2403      * @attr ref android.R.styleable#TextView_numeric
   2404      * @attr ref android.R.styleable#TextView_digits
   2405      * @attr ref android.R.styleable#TextView_phoneNumber
   2406      * @attr ref android.R.styleable#TextView_inputMethod
   2407      * @attr ref android.R.styleable#TextView_capitalize
   2408      * @attr ref android.R.styleable#TextView_autoText
   2409      */
   2410     public final KeyListener getKeyListener() {
   2411         return mEditor == null ? null : mEditor.mKeyListener;
   2412     }
   2413 
   2414     /**
   2415      * Sets the key listener to be used with this TextView.  This can be null
   2416      * to disallow user input.  Note that this method has significant and
   2417      * subtle interactions with soft keyboards and other input method:
   2418      * see {@link KeyListener#getInputType() KeyListener.getInputType()}
   2419      * for important details.  Calling this method will replace the current
   2420      * content type of the text view with the content type returned by the
   2421      * key listener.
   2422      * <p>
   2423      * Be warned that if you want a TextView with a key listener or movement
   2424      * method not to be focusable, or if you want a TextView without a
   2425      * key listener or movement method to be focusable, you must call
   2426      * {@link #setFocusable} again after calling this to get the focusability
   2427      * back the way you want it.
   2428      *
   2429      * @attr ref android.R.styleable#TextView_numeric
   2430      * @attr ref android.R.styleable#TextView_digits
   2431      * @attr ref android.R.styleable#TextView_phoneNumber
   2432      * @attr ref android.R.styleable#TextView_inputMethod
   2433      * @attr ref android.R.styleable#TextView_capitalize
   2434      * @attr ref android.R.styleable#TextView_autoText
   2435      */
   2436     public void setKeyListener(KeyListener input) {
   2437         mListenerChanged = true;
   2438         setKeyListenerOnly(input);
   2439         fixFocusableAndClickableSettings();
   2440 
   2441         if (input != null) {
   2442             createEditorIfNeeded();
   2443             setInputTypeFromEditor();
   2444         } else {
   2445             if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
   2446         }
   2447 
   2448         InputMethodManager imm = getInputMethodManager();
   2449         if (imm != null) imm.restartInput(this);
   2450     }
   2451 
   2452     private void setInputTypeFromEditor() {
   2453         try {
   2454             mEditor.mInputType = mEditor.mKeyListener.getInputType();
   2455         } catch (IncompatibleClassChangeError e) {
   2456             mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
   2457         }
   2458         // Change inputType, without affecting transformation.
   2459         // No need to applySingleLine since mSingleLine is unchanged.
   2460         setInputTypeSingleLine(mSingleLine);
   2461     }
   2462 
   2463     private void setKeyListenerOnly(KeyListener input) {
   2464         if (mEditor == null && input == null) return; // null is the default value
   2465 
   2466         createEditorIfNeeded();
   2467         if (mEditor.mKeyListener != input) {
   2468             mEditor.mKeyListener = input;
   2469             if (input != null && !(mText instanceof Editable)) {
   2470                 setText(mText);
   2471             }
   2472 
   2473             setFilters((Editable) mText, mFilters);
   2474         }
   2475     }
   2476 
   2477     /**
   2478      * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
   2479      * which provides positioning, scrolling, and text selection functionality.
   2480      * This will frequently be null for non-EditText TextViews.
   2481      * @return the movement method being used for this TextView.
   2482      * @see android.text.method.MovementMethod
   2483      */
   2484     public final MovementMethod getMovementMethod() {
   2485         return mMovement;
   2486     }
   2487 
   2488     /**
   2489      * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
   2490      * for this TextView. This can be null to disallow using the arrow keys to move the
   2491      * cursor or scroll the view.
   2492      * <p>
   2493      * Be warned that if you want a TextView with a key listener or movement
   2494      * method not to be focusable, or if you want a TextView without a
   2495      * key listener or movement method to be focusable, you must call
   2496      * {@link #setFocusable} again after calling this to get the focusability
   2497      * back the way you want it.
   2498      */
   2499     public final void setMovementMethod(MovementMethod movement) {
   2500         if (mMovement != movement) {
   2501             mMovement = movement;
   2502 
   2503             if (movement != null && mSpannable == null) {
   2504                 setText(mText);
   2505             }
   2506 
   2507             fixFocusableAndClickableSettings();
   2508 
   2509             // SelectionModifierCursorController depends on textCanBeSelected, which depends on
   2510             // mMovement
   2511             if (mEditor != null) mEditor.prepareCursorControllers();
   2512         }
   2513     }
   2514 
   2515     private void fixFocusableAndClickableSettings() {
   2516         if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
   2517             setFocusable(FOCUSABLE);
   2518             setClickable(true);
   2519             setLongClickable(true);
   2520         } else {
   2521             setFocusable(FOCUSABLE_AUTO);
   2522             setClickable(false);
   2523             setLongClickable(false);
   2524         }
   2525     }
   2526 
   2527     /**
   2528      * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
   2529      * This is frequently null, except for single-line and password fields.
   2530      * @return the current transformation method for this TextView.
   2531      *
   2532      * @attr ref android.R.styleable#TextView_password
   2533      * @attr ref android.R.styleable#TextView_singleLine
   2534      */
   2535     public final TransformationMethod getTransformationMethod() {
   2536         return mTransformation;
   2537     }
   2538 
   2539     /**
   2540      * Sets the transformation that is applied to the text that this
   2541      * TextView is displaying.
   2542      *
   2543      * @attr ref android.R.styleable#TextView_password
   2544      * @attr ref android.R.styleable#TextView_singleLine
   2545      */
   2546     public final void setTransformationMethod(TransformationMethod method) {
   2547         if (method == mTransformation) {
   2548             // Avoid the setText() below if the transformation is
   2549             // the same.
   2550             return;
   2551         }
   2552         if (mTransformation != null) {
   2553             if (mSpannable != null) {
   2554                 mSpannable.removeSpan(mTransformation);
   2555             }
   2556         }
   2557 
   2558         mTransformation = method;
   2559 
   2560         if (method instanceof TransformationMethod2) {
   2561             TransformationMethod2 method2 = (TransformationMethod2) method;
   2562             mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
   2563             method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
   2564         } else {
   2565             mAllowTransformationLengthChange = false;
   2566         }
   2567 
   2568         setText(mText);
   2569 
   2570         if (hasPasswordTransformationMethod()) {
   2571             notifyViewAccessibilityStateChangedIfNeeded(
   2572                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
   2573         }
   2574 
   2575         // PasswordTransformationMethod always have LTR text direction heuristics returned by
   2576         // getTextDirectionHeuristic, needs reset
   2577         mTextDir = getTextDirectionHeuristic();
   2578     }
   2579 
   2580     /**
   2581      * Returns the top padding of the view, plus space for the top
   2582      * Drawable if any.
   2583      */
   2584     public int getCompoundPaddingTop() {
   2585         final Drawables dr = mDrawables;
   2586         if (dr == null || dr.mShowing[Drawables.TOP] == null) {
   2587             return mPaddingTop;
   2588         } else {
   2589             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
   2590         }
   2591     }
   2592 
   2593     /**
   2594      * Returns the bottom padding of the view, plus space for the bottom
   2595      * Drawable if any.
   2596      */
   2597     public int getCompoundPaddingBottom() {
   2598         final Drawables dr = mDrawables;
   2599         if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
   2600             return mPaddingBottom;
   2601         } else {
   2602             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
   2603         }
   2604     }
   2605 
   2606     /**
   2607      * Returns the left padding of the view, plus space for the left
   2608      * Drawable if any.
   2609      */
   2610     public int getCompoundPaddingLeft() {
   2611         final Drawables dr = mDrawables;
   2612         if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
   2613             return mPaddingLeft;
   2614         } else {
   2615             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
   2616         }
   2617     }
   2618 
   2619     /**
   2620      * Returns the right padding of the view, plus space for the right
   2621      * Drawable if any.
   2622      */
   2623     public int getCompoundPaddingRight() {
   2624         final Drawables dr = mDrawables;
   2625         if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
   2626             return mPaddingRight;
   2627         } else {
   2628             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
   2629         }
   2630     }
   2631 
   2632     /**
   2633      * Returns the start padding of the view, plus space for the start
   2634      * Drawable if any.
   2635      */
   2636     public int getCompoundPaddingStart() {
   2637         resolveDrawables();
   2638         switch(getLayoutDirection()) {
   2639             default:
   2640             case LAYOUT_DIRECTION_LTR:
   2641                 return getCompoundPaddingLeft();
   2642             case LAYOUT_DIRECTION_RTL:
   2643                 return getCompoundPaddingRight();
   2644         }
   2645     }
   2646 
   2647     /**
   2648      * Returns the end padding of the view, plus space for the end
   2649      * Drawable if any.
   2650      */
   2651     public int getCompoundPaddingEnd() {
   2652         resolveDrawables();
   2653         switch(getLayoutDirection()) {
   2654             default:
   2655             case LAYOUT_DIRECTION_LTR:
   2656                 return getCompoundPaddingRight();
   2657             case LAYOUT_DIRECTION_RTL:
   2658                 return getCompoundPaddingLeft();
   2659         }
   2660     }
   2661 
   2662     /**
   2663      * Returns the extended top padding of the view, including both the
   2664      * top Drawable if any and any extra space to keep more than maxLines
   2665      * of text from showing.  It is only valid to call this after measuring.
   2666      */
   2667     public int getExtendedPaddingTop() {
   2668         if (mMaxMode != LINES) {
   2669             return getCompoundPaddingTop();
   2670         }
   2671 
   2672         if (mLayout == null) {
   2673             assumeLayout();
   2674         }
   2675 
   2676         if (mLayout.getLineCount() <= mMaximum) {
   2677             return getCompoundPaddingTop();
   2678         }
   2679 
   2680         int top = getCompoundPaddingTop();
   2681         int bottom = getCompoundPaddingBottom();
   2682         int viewht = getHeight() - top - bottom;
   2683         int layoutht = mLayout.getLineTop(mMaximum);
   2684 
   2685         if (layoutht >= viewht) {
   2686             return top;
   2687         }
   2688 
   2689         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   2690         if (gravity == Gravity.TOP) {
   2691             return top;
   2692         } else if (gravity == Gravity.BOTTOM) {
   2693             return top + viewht - layoutht;
   2694         } else { // (gravity == Gravity.CENTER_VERTICAL)
   2695             return top + (viewht - layoutht) / 2;
   2696         }
   2697     }
   2698 
   2699     /**
   2700      * Returns the extended bottom padding of the view, including both the
   2701      * bottom Drawable if any and any extra space to keep more than maxLines
   2702      * of text from showing.  It is only valid to call this after measuring.
   2703      */
   2704     public int getExtendedPaddingBottom() {
   2705         if (mMaxMode != LINES) {
   2706             return getCompoundPaddingBottom();
   2707         }
   2708 
   2709         if (mLayout == null) {
   2710             assumeLayout();
   2711         }
   2712 
   2713         if (mLayout.getLineCount() <= mMaximum) {
   2714             return getCompoundPaddingBottom();
   2715         }
   2716 
   2717         int top = getCompoundPaddingTop();
   2718         int bottom = getCompoundPaddingBottom();
   2719         int viewht = getHeight() - top - bottom;
   2720         int layoutht = mLayout.getLineTop(mMaximum);
   2721 
   2722         if (layoutht >= viewht) {
   2723             return bottom;
   2724         }
   2725 
   2726         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   2727         if (gravity == Gravity.TOP) {
   2728             return bottom + viewht - layoutht;
   2729         } else if (gravity == Gravity.BOTTOM) {
   2730             return bottom;
   2731         } else { // (gravity == Gravity.CENTER_VERTICAL)
   2732             return bottom + (viewht - layoutht) / 2;
   2733         }
   2734     }
   2735 
   2736     /**
   2737      * Returns the total left padding of the view, including the left
   2738      * Drawable if any.
   2739      */
   2740     public int getTotalPaddingLeft() {
   2741         return getCompoundPaddingLeft();
   2742     }
   2743 
   2744     /**
   2745      * Returns the total right padding of the view, including the right
   2746      * Drawable if any.
   2747      */
   2748     public int getTotalPaddingRight() {
   2749         return getCompoundPaddingRight();
   2750     }
   2751 
   2752     /**
   2753      * Returns the total start padding of the view, including the start
   2754      * Drawable if any.
   2755      */
   2756     public int getTotalPaddingStart() {
   2757         return getCompoundPaddingStart();
   2758     }
   2759 
   2760     /**
   2761      * Returns the total end padding of the view, including the end
   2762      * Drawable if any.
   2763      */
   2764     public int getTotalPaddingEnd() {
   2765         return getCompoundPaddingEnd();
   2766     }
   2767 
   2768     /**
   2769      * Returns the total top padding of the view, including the top
   2770      * Drawable if any, the extra space to keep more than maxLines
   2771      * from showing, and the vertical offset for gravity, if any.
   2772      */
   2773     public int getTotalPaddingTop() {
   2774         return getExtendedPaddingTop() + getVerticalOffset(true);
   2775     }
   2776 
   2777     /**
   2778      * Returns the total bottom padding of the view, including the bottom
   2779      * Drawable if any, the extra space to keep more than maxLines
   2780      * from showing, and the vertical offset for gravity, if any.
   2781      */
   2782     public int getTotalPaddingBottom() {
   2783         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
   2784     }
   2785 
   2786     /**
   2787      * Sets the Drawables (if any) to appear to the left of, above, to the
   2788      * right of, and below the text. Use {@code null} if you do not want a
   2789      * Drawable there. The Drawables must already have had
   2790      * {@link Drawable#setBounds} called.
   2791      * <p>
   2792      * Calling this method will overwrite any Drawables previously set using
   2793      * {@link #setCompoundDrawablesRelative} or related methods.
   2794      *
   2795      * @attr ref android.R.styleable#TextView_drawableLeft
   2796      * @attr ref android.R.styleable#TextView_drawableTop
   2797      * @attr ref android.R.styleable#TextView_drawableRight
   2798      * @attr ref android.R.styleable#TextView_drawableBottom
   2799      */
   2800     public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
   2801             @Nullable Drawable right, @Nullable Drawable bottom) {
   2802         Drawables dr = mDrawables;
   2803 
   2804         // We're switching to absolute, discard relative.
   2805         if (dr != null) {
   2806             if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
   2807             dr.mDrawableStart = null;
   2808             if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
   2809             dr.mDrawableEnd = null;
   2810             dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
   2811             dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
   2812         }
   2813 
   2814         final boolean drawables = left != null || top != null || right != null || bottom != null;
   2815         if (!drawables) {
   2816             // Clearing drawables...  can we free the data structure?
   2817             if (dr != null) {
   2818                 if (!dr.hasMetadata()) {
   2819                     mDrawables = null;
   2820                 } else {
   2821                     // We need to retain the last set padding, so just clear
   2822                     // out all of the fields in the existing structure.
   2823                     for (int i = dr.mShowing.length - 1; i >= 0; i--) {
   2824                         if (dr.mShowing[i] != null) {
   2825                             dr.mShowing[i].setCallback(null);
   2826                         }
   2827                         dr.mShowing[i] = null;
   2828                     }
   2829                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   2830                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   2831                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   2832                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   2833                 }
   2834             }
   2835         } else {
   2836             if (dr == null) {
   2837                 mDrawables = dr = new Drawables(getContext());
   2838             }
   2839 
   2840             mDrawables.mOverride = false;
   2841 
   2842             if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
   2843                 dr.mShowing[Drawables.LEFT].setCallback(null);
   2844             }
   2845             dr.mShowing[Drawables.LEFT] = left;
   2846 
   2847             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
   2848                 dr.mShowing[Drawables.TOP].setCallback(null);
   2849             }
   2850             dr.mShowing[Drawables.TOP] = top;
   2851 
   2852             if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
   2853                 dr.mShowing[Drawables.RIGHT].setCallback(null);
   2854             }
   2855             dr.mShowing[Drawables.RIGHT] = right;
   2856 
   2857             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
   2858                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
   2859             }
   2860             dr.mShowing[Drawables.BOTTOM] = bottom;
   2861 
   2862             final Rect compoundRect = dr.mCompoundRect;
   2863             int[] state;
   2864 
   2865             state = getDrawableState();
   2866 
   2867             if (left != null) {
   2868                 left.setState(state);
   2869                 left.copyBounds(compoundRect);
   2870                 left.setCallback(this);
   2871                 dr.mDrawableSizeLeft = compoundRect.width();
   2872                 dr.mDrawableHeightLeft = compoundRect.height();
   2873             } else {
   2874                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   2875             }
   2876 
   2877             if (right != null) {
   2878                 right.setState(state);
   2879                 right.copyBounds(compoundRect);
   2880                 right.setCallback(this);
   2881                 dr.mDrawableSizeRight = compoundRect.width();
   2882                 dr.mDrawableHeightRight = compoundRect.height();
   2883             } else {
   2884                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   2885             }
   2886 
   2887             if (top != null) {
   2888                 top.setState(state);
   2889                 top.copyBounds(compoundRect);
   2890                 top.setCallback(this);
   2891                 dr.mDrawableSizeTop = compoundRect.height();
   2892                 dr.mDrawableWidthTop = compoundRect.width();
   2893             } else {
   2894                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   2895             }
   2896 
   2897             if (bottom != null) {
   2898                 bottom.setState(state);
   2899                 bottom.copyBounds(compoundRect);
   2900                 bottom.setCallback(this);
   2901                 dr.mDrawableSizeBottom = compoundRect.height();
   2902                 dr.mDrawableWidthBottom = compoundRect.width();
   2903             } else {
   2904                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   2905             }
   2906         }
   2907 
   2908         // Save initial left/right drawables
   2909         if (dr != null) {
   2910             dr.mDrawableLeftInitial = left;
   2911             dr.mDrawableRightInitial = right;
   2912         }
   2913 
   2914         resetResolvedDrawables();
   2915         resolveDrawables();
   2916         applyCompoundDrawableTint();
   2917         invalidate();
   2918         requestLayout();
   2919     }
   2920 
   2921     /**
   2922      * Sets the Drawables (if any) to appear to the left of, above, to the
   2923      * right of, and below the text. Use 0 if you do not want a Drawable there.
   2924      * The Drawables' bounds will be set to their intrinsic bounds.
   2925      * <p>
   2926      * Calling this method will overwrite any Drawables previously set using
   2927      * {@link #setCompoundDrawablesRelative} or related methods.
   2928      *
   2929      * @param left Resource identifier of the left Drawable.
   2930      * @param top Resource identifier of the top Drawable.
   2931      * @param right Resource identifier of the right Drawable.
   2932      * @param bottom Resource identifier of the bottom Drawable.
   2933      *
   2934      * @attr ref android.R.styleable#TextView_drawableLeft
   2935      * @attr ref android.R.styleable#TextView_drawableTop
   2936      * @attr ref android.R.styleable#TextView_drawableRight
   2937      * @attr ref android.R.styleable#TextView_drawableBottom
   2938      */
   2939     @android.view.RemotableViewMethod
   2940     public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
   2941             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
   2942         final Context context = getContext();
   2943         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
   2944                 top != 0 ? context.getDrawable(top) : null,
   2945                 right != 0 ? context.getDrawable(right) : null,
   2946                 bottom != 0 ? context.getDrawable(bottom) : null);
   2947     }
   2948 
   2949     /**
   2950      * Sets the Drawables (if any) to appear to the left of, above, to the
   2951      * right of, and below the text. Use {@code null} if you do not want a
   2952      * Drawable there. The Drawables' bounds will be set to their intrinsic
   2953      * bounds.
   2954      * <p>
   2955      * Calling this method will overwrite any Drawables previously set using
   2956      * {@link #setCompoundDrawablesRelative} or related methods.
   2957      *
   2958      * @attr ref android.R.styleable#TextView_drawableLeft
   2959      * @attr ref android.R.styleable#TextView_drawableTop
   2960      * @attr ref android.R.styleable#TextView_drawableRight
   2961      * @attr ref android.R.styleable#TextView_drawableBottom
   2962      */
   2963     @android.view.RemotableViewMethod
   2964     public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
   2965             @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
   2966 
   2967         if (left != null) {
   2968             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
   2969         }
   2970         if (right != null) {
   2971             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
   2972         }
   2973         if (top != null) {
   2974             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
   2975         }
   2976         if (bottom != null) {
   2977             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
   2978         }
   2979         setCompoundDrawables(left, top, right, bottom);
   2980     }
   2981 
   2982     /**
   2983      * Sets the Drawables (if any) to appear to the start of, above, to the end
   2984      * of, and below the text. Use {@code null} if you do not want a Drawable
   2985      * there. The Drawables must already have had {@link Drawable#setBounds}
   2986      * called.
   2987      * <p>
   2988      * Calling this method will overwrite any Drawables previously set using
   2989      * {@link #setCompoundDrawables} or related methods.
   2990      *
   2991      * @attr ref android.R.styleable#TextView_drawableStart
   2992      * @attr ref android.R.styleable#TextView_drawableTop
   2993      * @attr ref android.R.styleable#TextView_drawableEnd
   2994      * @attr ref android.R.styleable#TextView_drawableBottom
   2995      */
   2996     @android.view.RemotableViewMethod
   2997     public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
   2998             @Nullable Drawable end, @Nullable Drawable bottom) {
   2999         Drawables dr = mDrawables;
   3000 
   3001         // We're switching to relative, discard absolute.
   3002         if (dr != null) {
   3003             if (dr.mShowing[Drawables.LEFT] != null) {
   3004                 dr.mShowing[Drawables.LEFT].setCallback(null);
   3005             }
   3006             dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
   3007             if (dr.mShowing[Drawables.RIGHT] != null) {
   3008                 dr.mShowing[Drawables.RIGHT].setCallback(null);
   3009             }
   3010             dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
   3011             dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
   3012             dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
   3013         }
   3014 
   3015         final boolean drawables = start != null || top != null
   3016                 || end != null || bottom != null;
   3017 
   3018         if (!drawables) {
   3019             // Clearing drawables...  can we free the data structure?
   3020             if (dr != null) {
   3021                 if (!dr.hasMetadata()) {
   3022                     mDrawables = null;
   3023                 } else {
   3024                     // We need to retain the last set padding, so just clear
   3025                     // out all of the fields in the existing structure.
   3026                     if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
   3027                     dr.mDrawableStart = null;
   3028                     if (dr.mShowing[Drawables.TOP] != null) {
   3029                         dr.mShowing[Drawables.TOP].setCallback(null);
   3030                     }
   3031                     dr.mShowing[Drawables.TOP] = null;
   3032                     if (dr.mDrawableEnd != null) {
   3033                         dr.mDrawableEnd.setCallback(null);
   3034                     }
   3035                     dr.mDrawableEnd = null;
   3036                     if (dr.mShowing[Drawables.BOTTOM] != null) {
   3037                         dr.mShowing[Drawables.BOTTOM].setCallback(null);
   3038                     }
   3039                     dr.mShowing[Drawables.BOTTOM] = null;
   3040                     dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
   3041                     dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
   3042                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   3043                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   3044                 }
   3045             }
   3046         } else {
   3047             if (dr == null) {
   3048                 mDrawables = dr = new Drawables(getContext());
   3049             }
   3050 
   3051             mDrawables.mOverride = true;
   3052 
   3053             if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
   3054                 dr.mDrawableStart.setCallback(null);
   3055             }
   3056             dr.mDrawableStart = start;
   3057 
   3058             if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
   3059                 dr.mShowing[Drawables.TOP].setCallback(null);
   3060             }
   3061             dr.mShowing[Drawables.TOP] = top;
   3062 
   3063             if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
   3064                 dr.mDrawableEnd.setCallback(null);
   3065             }
   3066             dr.mDrawableEnd = end;
   3067 
   3068             if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
   3069                 dr.mShowing[Drawables.BOTTOM].setCallback(null);
   3070             }
   3071             dr.mShowing[Drawables.BOTTOM] = bottom;
   3072 
   3073             final Rect compoundRect = dr.mCompoundRect;
   3074             int[] state;
   3075 
   3076             state = getDrawableState();
   3077 
   3078             if (start != null) {
   3079                 start.setState(state);
   3080                 start.copyBounds(compoundRect);
   3081                 start.setCallback(this);
   3082                 dr.mDrawableSizeStart = compoundRect.width();
   3083                 dr.mDrawableHeightStart = compoundRect.height();
   3084             } else {
   3085                 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
   3086             }
   3087 
   3088             if (end != null) {
   3089                 end.setState(state);
   3090                 end.copyBounds(compoundRect);
   3091                 end.setCallback(this);
   3092                 dr.mDrawableSizeEnd = compoundRect.width();
   3093                 dr.mDrawableHeightEnd = compoundRect.height();
   3094             } else {
   3095                 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
   3096             }
   3097 
   3098             if (top != null) {
   3099                 top.setState(state);
   3100                 top.copyBounds(compoundRect);
   3101                 top.setCallback(this);
   3102                 dr.mDrawableSizeTop = compoundRect.height();
   3103                 dr.mDrawableWidthTop = compoundRect.width();
   3104             } else {
   3105                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
   3106             }
   3107 
   3108             if (bottom != null) {
   3109                 bottom.setState(state);
   3110                 bottom.copyBounds(compoundRect);
   3111                 bottom.setCallback(this);
   3112                 dr.mDrawableSizeBottom = compoundRect.height();
   3113                 dr.mDrawableWidthBottom = compoundRect.width();
   3114             } else {
   3115                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
   3116             }
   3117         }
   3118 
   3119         resetResolvedDrawables();
   3120         resolveDrawables();
   3121         invalidate();
   3122         requestLayout();
   3123     }
   3124 
   3125     /**
   3126      * Sets the Drawables (if any) to appear to the start of, above, to the end
   3127      * of, and below the text. Use 0 if you do not want a Drawable there. The
   3128      * Drawables' bounds will be set to their intrinsic bounds.
   3129      * <p>
   3130      * Calling this method will overwrite any Drawables previously set using
   3131      * {@link #setCompoundDrawables} or related methods.
   3132      *
   3133      * @param start Resource identifier of the start Drawable.
   3134      * @param top Resource identifier of the top Drawable.
   3135      * @param end Resource identifier of the end Drawable.
   3136      * @param bottom Resource identifier of the bottom Drawable.
   3137      *
   3138      * @attr ref android.R.styleable#TextView_drawableStart
   3139      * @attr ref android.R.styleable#TextView_drawableTop
   3140      * @attr ref android.R.styleable#TextView_drawableEnd
   3141      * @attr ref android.R.styleable#TextView_drawableBottom
   3142      */
   3143     @android.view.RemotableViewMethod
   3144     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
   3145             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
   3146         final Context context = getContext();
   3147         setCompoundDrawablesRelativeWithIntrinsicBounds(
   3148                 start != 0 ? context.getDrawable(start) : null,
   3149                 top != 0 ? context.getDrawable(top) : null,
   3150                 end != 0 ? context.getDrawable(end) : null,
   3151                 bottom != 0 ? context.getDrawable(bottom) : null);
   3152     }
   3153 
   3154     /**
   3155      * Sets the Drawables (if any) to appear to the start of, above, to the end
   3156      * of, and below the text. Use {@code null} if you do not want a Drawable
   3157      * there. The Drawables' bounds will be set to their intrinsic bounds.
   3158      * <p>
   3159      * Calling this method will overwrite any Drawables previously set using
   3160      * {@link #setCompoundDrawables} or related methods.
   3161      *
   3162      * @attr ref android.R.styleable#TextView_drawableStart
   3163      * @attr ref android.R.styleable#TextView_drawableTop
   3164      * @attr ref android.R.styleable#TextView_drawableEnd
   3165      * @attr ref android.R.styleable#TextView_drawableBottom
   3166      */
   3167     @android.view.RemotableViewMethod
   3168     public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
   3169             @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
   3170 
   3171         if (start != null) {
   3172             start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
   3173         }
   3174         if (end != null) {
   3175             end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
   3176         }
   3177         if (top != null) {
   3178             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
   3179         }
   3180         if (bottom != null) {
   3181             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
   3182         }
   3183         setCompoundDrawablesRelative(start, top, end, bottom);
   3184     }
   3185 
   3186     /**
   3187      * Returns drawables for the left, top, right, and bottom borders.
   3188      *
   3189      * @attr ref android.R.styleable#TextView_drawableLeft
   3190      * @attr ref android.R.styleable#TextView_drawableTop
   3191      * @attr ref android.R.styleable#TextView_drawableRight
   3192      * @attr ref android.R.styleable#TextView_drawableBottom
   3193      */
   3194     @NonNull
   3195     public Drawable[] getCompoundDrawables() {
   3196         final Drawables dr = mDrawables;
   3197         if (dr != null) {
   3198             return dr.mShowing.clone();
   3199         } else {
   3200             return new Drawable[] { null, null, null, null };
   3201         }
   3202     }
   3203 
   3204     /**
   3205      * Returns drawables for the start, top, end, and bottom borders.
   3206      *
   3207      * @attr ref android.R.styleable#TextView_drawableStart
   3208      * @attr ref android.R.styleable#TextView_drawableTop
   3209      * @attr ref android.R.styleable#TextView_drawableEnd
   3210      * @attr ref android.R.styleable#TextView_drawableBottom
   3211      */
   3212     @NonNull
   3213     public Drawable[] getCompoundDrawablesRelative() {
   3214         final Drawables dr = mDrawables;
   3215         if (dr != null) {
   3216             return new Drawable[] {
   3217                 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
   3218                 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
   3219             };
   3220         } else {
   3221             return new Drawable[] { null, null, null, null };
   3222         }
   3223     }
   3224 
   3225     /**
   3226      * Sets the size of the padding between the compound drawables and
   3227      * the text.
   3228      *
   3229      * @attr ref android.R.styleable#TextView_drawablePadding
   3230      */
   3231     @android.view.RemotableViewMethod
   3232     public void setCompoundDrawablePadding(int pad) {
   3233         Drawables dr = mDrawables;
   3234         if (pad == 0) {
   3235             if (dr != null) {
   3236                 dr.mDrawablePadding = pad;
   3237             }
   3238         } else {
   3239             if (dr == null) {
   3240                 mDrawables = dr = new Drawables(getContext());
   3241             }
   3242             dr.mDrawablePadding = pad;
   3243         }
   3244 
   3245         invalidate();
   3246         requestLayout();
   3247     }
   3248 
   3249     /**
   3250      * Returns the padding between the compound drawables and the text.
   3251      *
   3252      * @attr ref android.R.styleable#TextView_drawablePadding
   3253      */
   3254     @InspectableProperty(name = "drawablePadding")
   3255     public int getCompoundDrawablePadding() {
   3256         final Drawables dr = mDrawables;
   3257         return dr != null ? dr.mDrawablePadding : 0;
   3258     }
   3259 
   3260     /**
   3261      * Applies a tint to the compound drawables. Does not modify the
   3262      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
   3263      * <p>
   3264      * Subsequent calls to
   3265      * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
   3266      * and related methods will automatically mutate the drawables and apply
   3267      * the specified tint and tint mode using
   3268      * {@link Drawable#setTintList(ColorStateList)}.
   3269      *
   3270      * @param tint the tint to apply, may be {@code null} to clear tint
   3271      *
   3272      * @attr ref android.R.styleable#TextView_drawableTint
   3273      * @see #getCompoundDrawableTintList()
   3274      * @see Drawable#setTintList(ColorStateList)
   3275      */
   3276     public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
   3277         if (mDrawables == null) {
   3278             mDrawables = new Drawables(getContext());
   3279         }
   3280         mDrawables.mTintList = tint;
   3281         mDrawables.mHasTint = true;
   3282 
   3283         applyCompoundDrawableTint();
   3284     }
   3285 
   3286     /**
   3287      * @return the tint applied to the compound drawables
   3288      * @attr ref android.R.styleable#TextView_drawableTint
   3289      * @see #setCompoundDrawableTintList(ColorStateList)
   3290      */
   3291     @InspectableProperty(name = "drawableTint")
   3292     public ColorStateList getCompoundDrawableTintList() {
   3293         return mDrawables != null ? mDrawables.mTintList : null;
   3294     }
   3295 
   3296     /**
   3297      * Specifies the blending mode used to apply the tint specified by
   3298      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
   3299      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
   3300      *
   3301      * @param tintMode the blending mode used to apply the tint, may be
   3302      *                 {@code null} to clear tint
   3303      * @attr ref android.R.styleable#TextView_drawableTintMode
   3304      * @see #setCompoundDrawableTintList(ColorStateList)
   3305      * @see Drawable#setTintMode(PorterDuff.Mode)
   3306      */
   3307     public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
   3308         setCompoundDrawableTintBlendMode(tintMode != null
   3309                 ? BlendMode.fromValue(tintMode.nativeInt) : null);
   3310     }
   3311 
   3312     /**
   3313      * Specifies the blending mode used to apply the tint specified by
   3314      * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
   3315      * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
   3316      *
   3317      * @param blendMode the blending mode used to apply the tint, may be
   3318      *                 {@code null} to clear tint
   3319      * @attr ref android.R.styleable#TextView_drawableTintMode
   3320      * @see #setCompoundDrawableTintList(ColorStateList)
   3321      * @see Drawable#setTintBlendMode(BlendMode)
   3322      */
   3323     public void setCompoundDrawableTintBlendMode(@Nullable BlendMode blendMode) {
   3324         if (mDrawables == null) {
   3325             mDrawables = new Drawables(getContext());
   3326         }
   3327         mDrawables.mBlendMode = blendMode;
   3328         mDrawables.mHasTintMode = true;
   3329 
   3330         applyCompoundDrawableTint();
   3331     }
   3332 
   3333     /**
   3334      * Returns the blending mode used to apply the tint to the compound
   3335      * drawables, if specified.
   3336      *
   3337      * @return the blending mode used to apply the tint to the compound
   3338      *         drawables
   3339      * @attr ref android.R.styleable#TextView_drawableTintMode
   3340      * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
   3341      *
   3342      */
   3343     @InspectableProperty(name = "drawableTintMode")
   3344     public PorterDuff.Mode getCompoundDrawableTintMode() {
   3345         BlendMode mode = getCompoundDrawableTintBlendMode();
   3346         return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
   3347     }
   3348 
   3349     /**
   3350      * Returns the blending mode used to apply the tint to the compound
   3351      * drawables, if specified.
   3352      *
   3353      * @return the blending mode used to apply the tint to the compound
   3354      *         drawables
   3355      * @attr ref android.R.styleable#TextView_drawableTintMode
   3356      * @see #setCompoundDrawableTintBlendMode(BlendMode)
   3357      */
   3358     @InspectableProperty(name = "drawableBlendMode",
   3359             attributeId = com.android.internal.R.styleable.TextView_drawableTintMode)
   3360     public @Nullable BlendMode getCompoundDrawableTintBlendMode() {
   3361         return mDrawables != null ? mDrawables.mBlendMode : null;
   3362     }
   3363 
   3364     private void applyCompoundDrawableTint() {
   3365         if (mDrawables == null) {
   3366             return;
   3367         }
   3368 
   3369         if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
   3370             final ColorStateList tintList = mDrawables.mTintList;
   3371             final BlendMode blendMode = mDrawables.mBlendMode;
   3372             final boolean hasTint = mDrawables.mHasTint;
   3373             final boolean hasTintMode = mDrawables.mHasTintMode;
   3374             final int[] state = getDrawableState();
   3375 
   3376             for (Drawable dr : mDrawables.mShowing) {
   3377                 if (dr == null) {
   3378                     continue;
   3379                 }
   3380 
   3381                 if (dr == mDrawables.mDrawableError) {
   3382                     // From a developer's perspective, the error drawable isn't
   3383                     // a compound drawable. Don't apply the generic compound
   3384                     // drawable tint to it.
   3385                     continue;
   3386                 }
   3387 
   3388                 dr.mutate();
   3389 
   3390                 if (hasTint) {
   3391                     dr.setTintList(tintList);
   3392                 }
   3393 
   3394                 if (hasTintMode) {
   3395                     dr.setTintBlendMode(blendMode);
   3396                 }
   3397 
   3398                 // The drawable (or one of its children) may not have been
   3399                 // stateful before applying the tint, so let's try again.
   3400                 if (dr.isStateful()) {
   3401                     dr.setState(state);
   3402                 }
   3403             }
   3404         }
   3405     }
   3406 
   3407     /**
   3408      * @inheritDoc
   3409      *
   3410      * @see #setFirstBaselineToTopHeight(int)
   3411      * @see #setLastBaselineToBottomHeight(int)
   3412      */
   3413     @Override
   3414     public void setPadding(int left, int top, int right, int bottom) {
   3415         if (left != mPaddingLeft
   3416                 || right != mPaddingRight
   3417                 || top != mPaddingTop
   3418                 ||  bottom != mPaddingBottom) {
   3419             nullLayouts();
   3420         }
   3421 
   3422         // the super call will requestLayout()
   3423         super.setPadding(left, top, right, bottom);
   3424         invalidate();
   3425     }
   3426 
   3427     /**
   3428      * @inheritDoc
   3429      *
   3430      * @see #setFirstBaselineToTopHeight(int)
   3431      * @see #setLastBaselineToBottomHeight(int)
   3432      */
   3433     @Override
   3434     public void setPaddingRelative(int start, int top, int end, int bottom) {
   3435         if (start != getPaddingStart()
   3436                 || end != getPaddingEnd()
   3437                 || top != mPaddingTop
   3438                 || bottom != mPaddingBottom) {
   3439             nullLayouts();
   3440         }
   3441 
   3442         // the super call will requestLayout()
   3443         super.setPaddingRelative(start, top, end, bottom);
   3444         invalidate();
   3445     }
   3446 
   3447     /**
   3448      * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
   3449      * the distance between the top of the TextView and first line's baseline.
   3450      * <p>
   3451      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
   3452      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
   3453      *
   3454      * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
   3455      * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
   3456      * Moreover since this function sets the top padding, if the height of the TextView is less than
   3457      * the sum of top padding, line height and bottom padding, top of the line will be pushed
   3458      * down and bottom will be clipped.
   3459      *
   3460      * @param firstBaselineToTopHeight distance between first baseline to top of the container
   3461      *      in pixels
   3462      *
   3463      * @see #getFirstBaselineToTopHeight()
   3464      * @see #setLastBaselineToBottomHeight(int)
   3465      * @see #setPadding(int, int, int, int)
   3466      * @see #setPaddingRelative(int, int, int, int)
   3467      *
   3468      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
   3469      */
   3470     public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
   3471         Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
   3472 
   3473         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
   3474         final int fontMetricsTop;
   3475         if (getIncludeFontPadding()) {
   3476             fontMetricsTop = fontMetrics.top;
   3477         } else {
   3478             fontMetricsTop = fontMetrics.ascent;
   3479         }
   3480 
   3481         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
   3482         // in settings). At the moment, we don't.
   3483 
   3484         if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
   3485             final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
   3486             setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
   3487         }
   3488     }
   3489 
   3490     /**
   3491      * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
   3492      * the distance between the bottom of the TextView and the last line's baseline.
   3493      * <p>
   3494      * <img src="{@docRoot}reference/android/images/text/widget/first_last_baseline.png" />
   3495      * <figcaption>First and last baseline metrics for a TextView.</figcaption>
   3496      *
   3497      * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
   3498      * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
   3499      * Moreover since this function sets the bottom padding, if the height of the TextView is less
   3500      * than the sum of top padding, line height and bottom padding, bottom of the text will be
   3501      * clipped.
   3502      *
   3503      * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
   3504      *      in pixels
   3505      *
   3506      * @see #getLastBaselineToBottomHeight()
   3507      * @see #setFirstBaselineToTopHeight(int)
   3508      * @see #setPadding(int, int, int, int)
   3509      * @see #setPaddingRelative(int, int, int, int)
   3510      *
   3511      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
   3512      */
   3513     public void setLastBaselineToBottomHeight(
   3514             @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
   3515         Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
   3516 
   3517         final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
   3518         final int fontMetricsBottom;
   3519         if (getIncludeFontPadding()) {
   3520             fontMetricsBottom = fontMetrics.bottom;
   3521         } else {
   3522             fontMetricsBottom = fontMetrics.descent;
   3523         }
   3524 
   3525         // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
   3526         // in settings). At the moment, we don't.
   3527 
   3528         if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
   3529             final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
   3530             setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
   3531         }
   3532     }
   3533 
   3534     /**
   3535      * Returns the distance between the first text baseline and the top of this TextView.
   3536      *
   3537      * @see #setFirstBaselineToTopHeight(int)
   3538      * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
   3539      */
   3540     @InspectableProperty
   3541     public int getFirstBaselineToTopHeight() {
   3542         return getPaddingTop() - getPaint().getFontMetricsInt().top;
   3543     }
   3544 
   3545     /**
   3546      * Returns the distance between the last text baseline and the bottom of this TextView.
   3547      *
   3548      * @see #setLastBaselineToBottomHeight(int)
   3549      * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
   3550      */
   3551     @InspectableProperty
   3552     public int getLastBaselineToBottomHeight() {
   3553         return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
   3554     }
   3555 
   3556     /**
   3557      * Gets the autolink mask of the text.
   3558      *
   3559      * See {@link Linkify#ALL} and peers for possible values.
   3560      *
   3561      * @attr ref android.R.styleable#TextView_autoLink
   3562      */
   3563     @InspectableProperty(name = "autoLink", flagMapping = {
   3564             @FlagEntry(name = "web", target = Linkify.WEB_URLS),
   3565             @FlagEntry(name = "email", target = Linkify.EMAIL_ADDRESSES),
   3566             @FlagEntry(name = "phone", target = Linkify.PHONE_NUMBERS),
   3567             @FlagEntry(name = "map", target = Linkify.MAP_ADDRESSES)
   3568     })
   3569     public final int getAutoLinkMask() {
   3570         return mAutoLinkMask;
   3571     }
   3572 
   3573     /**
   3574      * Sets the Drawable corresponding to the selection handle used for
   3575      * positioning the cursor within text. The Drawable defaults to the value
   3576      * of the textSelectHandle attribute.
   3577      * Note that any change applied to the handle Drawable will not be visible
   3578      * until the handle is hidden and then drawn again.
   3579      *
   3580      * @see #setTextSelectHandle(int)
   3581      * @attr ref android.R.styleable#TextView_textSelectHandle
   3582      */
   3583     @android.view.RemotableViewMethod
   3584     public void setTextSelectHandle(@NonNull Drawable textSelectHandle) {
   3585         Preconditions.checkNotNull(textSelectHandle,
   3586                 "The text select handle should not be null.");
   3587         mTextSelectHandle = textSelectHandle;
   3588         mTextSelectHandleRes = 0;
   3589         if (mEditor != null) {
   3590             mEditor.loadHandleDrawables(true /* overwrite */);
   3591         }
   3592     }
   3593 
   3594     /**
   3595      * Sets the Drawable corresponding to the selection handle used for
   3596      * positioning the cursor within text. The Drawable defaults to the value
   3597      * of the textSelectHandle attribute.
   3598      * Note that any change applied to the handle Drawable will not be visible
   3599      * until the handle is hidden and then drawn again.
   3600      *
   3601      * @see #setTextSelectHandle(Drawable)
   3602      * @attr ref android.R.styleable#TextView_textSelectHandle
   3603      */
   3604     @android.view.RemotableViewMethod
   3605     public void setTextSelectHandle(@DrawableRes int textSelectHandle) {
   3606         Preconditions.checkArgument(textSelectHandle != 0,
   3607                 "The text select handle should be a valid drawable resource id.");
   3608         setTextSelectHandle(mContext.getDrawable(textSelectHandle));
   3609     }
   3610 
   3611     /**
   3612      * Returns the Drawable corresponding to the selection handle used
   3613      * for positioning the cursor within text.
   3614      * Note that any change applied to the handle Drawable will not be visible
   3615      * until the handle is hidden and then drawn again.
   3616      *
   3617      * @return the text select handle drawable
   3618      *
   3619      * @see #setTextSelectHandle(Drawable)
   3620      * @see #setTextSelectHandle(int)
   3621      * @attr ref android.R.styleable#TextView_textSelectHandle
   3622      */
   3623     @Nullable public Drawable getTextSelectHandle() {
   3624         if (mTextSelectHandle == null && mTextSelectHandleRes != 0) {
   3625             mTextSelectHandle = mContext.getDrawable(mTextSelectHandleRes);
   3626         }
   3627         return mTextSelectHandle;
   3628     }
   3629 
   3630     /**
   3631      * Sets the Drawable corresponding to the left handle used
   3632      * for selecting text. The Drawable defaults to the value of the
   3633      * textSelectHandleLeft attribute.
   3634      * Note that any change applied to the handle Drawable will not be visible
   3635      * until the handle is hidden and then drawn again.
   3636      *
   3637      * @see #setTextSelectHandleLeft(int)
   3638      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
   3639      */
   3640     @android.view.RemotableViewMethod
   3641     public void setTextSelectHandleLeft(@NonNull Drawable textSelectHandleLeft) {
   3642         Preconditions.checkNotNull(textSelectHandleLeft,
   3643                 "The left text select handle should not be null.");
   3644         mTextSelectHandleLeft = textSelectHandleLeft;
   3645         mTextSelectHandleLeftRes = 0;
   3646         if (mEditor != null) {
   3647             mEditor.loadHandleDrawables(true /* overwrite */);
   3648         }
   3649     }
   3650 
   3651     /**
   3652      * Sets the Drawable corresponding to the left handle used
   3653      * for selecting text. The Drawable defaults to the value of the
   3654      * textSelectHandleLeft attribute.
   3655      * Note that any change applied to the handle Drawable will not be visible
   3656      * until the handle is hidden and then drawn again.
   3657      *
   3658      * @see #setTextSelectHandleLeft(Drawable)
   3659      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
   3660      */
   3661     @android.view.RemotableViewMethod
   3662     public void setTextSelectHandleLeft(@DrawableRes int textSelectHandleLeft) {
   3663         Preconditions.checkArgument(textSelectHandleLeft != 0,
   3664                 "The text select left handle should be a valid drawable resource id.");
   3665         setTextSelectHandleLeft(mContext.getDrawable(textSelectHandleLeft));
   3666     }
   3667 
   3668     /**
   3669      * Returns the Drawable corresponding to the left handle used
   3670      * for selecting text.
   3671      * Note that any change applied to the handle Drawable will not be visible
   3672      * until the handle is hidden and then drawn again.
   3673      *
   3674      * @return the left text selection handle drawable
   3675      *
   3676      * @see #setTextSelectHandleLeft(Drawable)
   3677      * @see #setTextSelectHandleLeft(int)
   3678      * @attr ref android.R.styleable#TextView_textSelectHandleLeft
   3679      */
   3680     @Nullable public Drawable getTextSelectHandleLeft() {
   3681         if (mTextSelectHandleLeft == null && mTextSelectHandleLeftRes != 0) {
   3682             mTextSelectHandleLeft = mContext.getDrawable(mTextSelectHandleLeftRes);
   3683         }
   3684         return mTextSelectHandleLeft;
   3685     }
   3686 
   3687     /**
   3688      * Sets the Drawable corresponding to the right handle used
   3689      * for selecting text. The Drawable defaults to the value of the
   3690      * textSelectHandleRight attribute.
   3691      * Note that any change applied to the handle Drawable will not be visible
   3692      * until the handle is hidden and then drawn again.
   3693      *
   3694      * @see #setTextSelectHandleRight(int)
   3695      * @attr ref android.R.styleable#TextView_textSelectHandleRight
   3696      */
   3697     @android.view.RemotableViewMethod
   3698     public void setTextSelectHandleRight(@NonNull Drawable textSelectHandleRight) {
   3699         Preconditions.checkNotNull(textSelectHandleRight,
   3700                 "The right text select handle should not be null.");
   3701         mTextSelectHandleRight = textSelectHandleRight;
   3702         mTextSelectHandleRightRes = 0;
   3703         if (mEditor != null) {
   3704             mEditor.loadHandleDrawables(true /* overwrite */);
   3705         }
   3706     }
   3707 
   3708     /**
   3709      * Sets the Drawable corresponding to the right handle used
   3710      * for selecting text. The Drawable defaults to the value of the
   3711      * textSelectHandleRight attribute.
   3712      * Note that any change applied to the handle Drawable will not be visible
   3713      * until the handle is hidden and then drawn again.
   3714      *
   3715      * @see #setTextSelectHandleRight(Drawable)
   3716      * @attr ref android.R.styleable#TextView_textSelectHandleRight
   3717      */
   3718     @android.view.RemotableViewMethod
   3719     public void setTextSelectHandleRight(@DrawableRes int textSelectHandleRight) {
   3720         Preconditions.checkArgument(textSelectHandleRight != 0,
   3721                 "The text select right handle should be a valid drawable resource id.");
   3722         setTextSelectHandleRight(mContext.getDrawable(textSelectHandleRight));
   3723     }
   3724 
   3725     /**
   3726      * Returns the Drawable corresponding to the right handle used
   3727      * for selecting text.
   3728      * Note that any change applied to the handle Drawable will not be visible
   3729      * until the handle is hidden and then drawn again.
   3730      *
   3731      * @return the right text selection handle drawable
   3732      *
   3733      * @see #setTextSelectHandleRight(Drawable)
   3734      * @see #setTextSelectHandleRight(int)
   3735      * @attr ref android.R.styleable#TextView_textSelectHandleRight
   3736      */
   3737     @Nullable public Drawable getTextSelectHandleRight() {
   3738         if (mTextSelectHandleRight == null && mTextSelectHandleRightRes != 0) {
   3739             mTextSelectHandleRight = mContext.getDrawable(mTextSelectHandleRightRes);
   3740         }
   3741         return mTextSelectHandleRight;
   3742     }
   3743 
   3744     /**
   3745      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
   3746      * value of the textCursorDrawable attribute.
   3747      * Note that any change applied to the cursor Drawable will not be visible
   3748      * until the cursor is hidden and then drawn again.
   3749      *
   3750      * @see #setTextCursorDrawable(int)
   3751      * @attr ref android.R.styleable#TextView_textCursorDrawable
   3752      */
   3753     public void setTextCursorDrawable(@Nullable Drawable textCursorDrawable) {
   3754         mCursorDrawable = textCursorDrawable;
   3755         mCursorDrawableRes = 0;
   3756         if (mEditor != null) {
   3757             mEditor.loadCursorDrawable();
   3758         }
   3759     }
   3760 
   3761     /**
   3762      * Sets the Drawable corresponding to the text cursor. The Drawable defaults to the
   3763      * value of the textCursorDrawable attribute.
   3764      * Note that any change applied to the cursor Drawable will not be visible
   3765      * until the cursor is hidden and then drawn again.
   3766      *
   3767      * @see #setTextCursorDrawable(Drawable)
   3768      * @attr ref android.R.styleable#TextView_textCursorDrawable
   3769      */
   3770     public void setTextCursorDrawable(@DrawableRes int textCursorDrawable) {
   3771         setTextCursorDrawable(
   3772                 textCursorDrawable != 0 ? mContext.getDrawable(textCursorDrawable) : null);
   3773     }
   3774 
   3775     /**
   3776      * Returns the Drawable corresponding to the text cursor.
   3777      * Note that any change applied to the cursor Drawable will not be visible
   3778      * until the cursor is hidden and then drawn again.
   3779      *
   3780      * @return the text cursor drawable
   3781      *
   3782      * @see #setTextCursorDrawable(Drawable)
   3783      * @see #setTextCursorDrawable(int)
   3784      * @attr ref android.R.styleable#TextView_textCursorDrawable
   3785      */
   3786     @Nullable public Drawable getTextCursorDrawable() {
   3787         if (mCursorDrawable == null && mCursorDrawableRes != 0) {
   3788             mCursorDrawable = mContext.getDrawable(mCursorDrawableRes);
   3789         }
   3790         return mCursorDrawable;
   3791     }
   3792 
   3793     /**
   3794      * Sets the text appearance from the specified style resource.
   3795      * <p>
   3796      * Use a framework-defined {@code TextAppearance} style like
   3797      * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
   3798      * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
   3799      * set of attributes that can be used in a custom style.
   3800      *
   3801      * @param resId the resource identifier of the style to apply
   3802      * @attr ref android.R.styleable#TextView_textAppearance
   3803      */
   3804     @SuppressWarnings("deprecation")
   3805     public void setTextAppearance(@StyleRes int resId) {
   3806         setTextAppearance(mContext, resId);
   3807     }
   3808 
   3809     /**
   3810      * Sets the text color, size, style, hint color, and highlight color
   3811      * from the specified TextAppearance resource.
   3812      *
   3813      * @deprecated Use {@link #setTextAppearance(int)} instead.
   3814      */
   3815     @Deprecated
   3816     public void setTextAppearance(Context context, @StyleRes int resId) {
   3817         final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
   3818         final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
   3819         readTextAppearance(context, ta, attributes, false /* styleArray */);
   3820         ta.recycle();
   3821         applyTextAppearance(attributes);
   3822     }
   3823 
   3824     /**
   3825      * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
   3826      * that reads these attributes in the constructor and in {@link #setTextAppearance}.
   3827      */
   3828     private static class TextAppearanceAttributes {
   3829         int mTextColorHighlight = 0;
   3830         ColorStateList mTextColor = null;
   3831         ColorStateList mTextColorHint = null;
   3832         ColorStateList mTextColorLink = null;
   3833         int mTextSize = -1;
   3834         LocaleList mTextLocales = null;
   3835         String mFontFamily = null;
   3836         Typeface mFontTypeface = null;
   3837         boolean mFontFamilyExplicit = false;
   3838         int mTypefaceIndex = -1;
   3839         int mTextStyle = 0;
   3840         int mFontWeight = -1;
   3841         boolean mAllCaps = false;
   3842         int mShadowColor = 0;
   3843         float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
   3844         boolean mHasElegant = false;
   3845         boolean mElegant = false;
   3846         boolean mHasFallbackLineSpacing = false;
   3847         boolean mFallbackLineSpacing = false;
   3848         boolean mHasLetterSpacing = false;
   3849         float mLetterSpacing = 0;
   3850         String mFontFeatureSettings = null;
   3851         String mFontVariationSettings = null;
   3852 
   3853         @Override
   3854         public String toString() {
   3855             return "TextAppearanceAttributes {\n"
   3856                     + "    mTextColorHighlight:" + mTextColorHighlight + "\n"
   3857                     + "    mTextColor:" + mTextColor + "\n"
   3858                     + "    mTextColorHint:" + mTextColorHint + "\n"
   3859                     + "    mTextColorLink:" + mTextColorLink + "\n"
   3860                     + "    mTextSize:" + mTextSize + "\n"
   3861                     + "    mTextLocales:" + mTextLocales + "\n"
   3862                     + "    mFontFamily:" + mFontFamily + "\n"
   3863                     + "    mFontTypeface:" + mFontTypeface + "\n"
   3864                     + "    mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
   3865                     + "    mTypefaceIndex:" + mTypefaceIndex + "\n"
   3866                     + "    mTextStyle:" + mTextStyle + "\n"
   3867                     + "    mFontWeight:" + mFontWeight + "\n"
   3868                     + "    mAllCaps:" + mAllCaps + "\n"
   3869                     + "    mShadowColor:" + mShadowColor + "\n"
   3870                     + "    mShadowDx:" + mShadowDx + "\n"
   3871                     + "    mShadowDy:" + mShadowDy + "\n"
   3872                     + "    mShadowRadius:" + mShadowRadius + "\n"
   3873                     + "    mHasElegant:" + mHasElegant + "\n"
   3874                     + "    mElegant:" + mElegant + "\n"
   3875                     + "    mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
   3876                     + "    mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
   3877                     + "    mHasLetterSpacing:" + mHasLetterSpacing + "\n"
   3878                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
   3879                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
   3880                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
   3881                     + "}";
   3882         }
   3883     }
   3884 
   3885     // Maps styleable attributes that exist both in TextView style and TextAppearance.
   3886     private static final SparseIntArray sAppearanceValues = new SparseIntArray();
   3887     static {
   3888         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
   3889                 com.android.internal.R.styleable.TextAppearance_textColorHighlight);
   3890         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
   3891                 com.android.internal.R.styleable.TextAppearance_textColor);
   3892         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
   3893                 com.android.internal.R.styleable.TextAppearance_textColorHint);
   3894         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
   3895                 com.android.internal.R.styleable.TextAppearance_textColorLink);
   3896         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
   3897                 com.android.internal.R.styleable.TextAppearance_textSize);
   3898         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale,
   3899                 com.android.internal.R.styleable.TextAppearance_textLocale);
   3900         sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
   3901                 com.android.internal.R.styleable.TextAppearance_typeface);
   3902         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
   3903                 com.android.internal.R.styleable.TextAppearance_fontFamily);
   3904         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
   3905                 com.android.internal.R.styleable.TextAppearance_textStyle);
   3906         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight,
   3907                 com.android.internal.R.styleable.TextAppearance_textFontWeight);
   3908         sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
   3909                 com.android.internal.R.styleable.TextAppearance_textAllCaps);
   3910         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
   3911                 com.android.internal.R.styleable.TextAppearance_shadowColor);
   3912         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
   3913                 com.android.internal.R.styleable.TextAppearance_shadowDx);
   3914         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
   3915                 com.android.internal.R.styleable.TextAppearance_shadowDy);
   3916         sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
   3917                 com.android.internal.R.styleable.TextAppearance_shadowRadius);
   3918         sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
   3919                 com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
   3920         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
   3921                 com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
   3922         sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
   3923                 com.android.internal.R.styleable.TextAppearance_letterSpacing);
   3924         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
   3925                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
   3926         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
   3927                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
   3928     }
   3929 
   3930     /**
   3931      * Read the Text Appearance attributes from a given TypedArray and set its values to the given
   3932      * set. If the TypedArray contains a value that was already set in the given attributes, that
   3933      * will be overridden.
   3934      *
   3935      * @param context The Context to be used
   3936      * @param appearance The TypedArray to read properties from
   3937      * @param attributes the TextAppearanceAttributes to fill in
   3938      * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
   3939      *                   what attribute indexes will be used to read the properties.
   3940      */
   3941     private void readTextAppearance(Context context, TypedArray appearance,
   3942             TextAppearanceAttributes attributes, boolean styleArray) {
   3943         final int n = appearance.getIndexCount();
   3944         for (int i = 0; i < n; i++) {
   3945             final int attr = appearance.getIndex(i);
   3946             int index = attr;
   3947             // Translate style array index ids to TextAppearance ids.
   3948             if (styleArray) {
   3949                 index = sAppearanceValues.get(attr, -1);
   3950                 if (index == -1) {
   3951                     // This value is not part of a Text Appearance and should be ignored.
   3952                     continue;
   3953                 }
   3954             }
   3955             switch (index) {
   3956                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
   3957                     attributes.mTextColorHighlight =
   3958                             appearance.getColor(attr, attributes.mTextColorHighlight);
   3959                     break;
   3960                 case com.android.internal.R.styleable.TextAppearance_textColor:
   3961                     attributes.mTextColor = appearance.getColorStateList(attr);
   3962                     break;
   3963                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
   3964                     attributes.mTextColorHint = appearance.getColorStateList(attr);
   3965                     break;
   3966                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
   3967                     attributes.mTextColorLink = appearance.getColorStateList(attr);
   3968                     break;
   3969                 case com.android.internal.R.styleable.TextAppearance_textSize:
   3970                     attributes.mTextSize =
   3971                             appearance.getDimensionPixelSize(attr, attributes.mTextSize);
   3972                     break;
   3973                 case com.android.internal.R.styleable.TextAppearance_textLocale:
   3974                     final String localeString = appearance.getString(attr);
   3975                     if (localeString != null) {
   3976                         final LocaleList localeList = LocaleList.forLanguageTags(localeString);
   3977                         if (!localeList.isEmpty()) {
   3978                             attributes.mTextLocales = localeList;
   3979                         }
   3980                     }
   3981                     break;
   3982                 case com.android.internal.R.styleable.TextAppearance_typeface:
   3983                     attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
   3984                     if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
   3985                         attributes.mFontFamily = null;
   3986                     }
   3987                     break;
   3988                 case com.android.internal.R.styleable.TextAppearance_fontFamily:
   3989                     if (!context.isRestricted() && context.canLoadUnsafeResources()) {
   3990                         try {
   3991                             attributes.mFontTypeface = appearance.getFont(attr);
   3992                         } catch (UnsupportedOperationException | Resources.NotFoundException e) {
   3993                             // Expected if it is not a font resource.
   3994                         }
   3995                     }
   3996                     if (attributes.mFontTypeface == null) {
   3997                         attributes.mFontFamily = appearance.getString(attr);
   3998                     }
   3999                     attributes.mFontFamilyExplicit = true;
   4000                     break;
   4001                 case com.android.internal.R.styleable.TextAppearance_textStyle:
   4002                     attributes.mTextStyle = appearance.getInt(attr, attributes.mTextStyle);
   4003                     break;
   4004                 case com.android.internal.R.styleable.TextAppearance_textFontWeight:
   4005                     attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight);
   4006                     break;
   4007                 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
   4008                     attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
   4009                     break;
   4010                 case com.android.internal.R.styleable.TextAppearance_shadowColor:
   4011                     attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
   4012                     break;
   4013                 case com.android.internal.R.styleable.TextAppearance_shadowDx:
   4014                     attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
   4015                     break;
   4016                 case com.android.internal.R.styleable.TextAppearance_shadowDy:
   4017                     attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
   4018                     break;
   4019                 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
   4020                     attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
   4021                     break;
   4022                 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
   4023                     attributes.mHasElegant = true;
   4024                     attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
   4025                     break;
   4026                 case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
   4027                     attributes.mHasFallbackLineSpacing = true;
   4028                     attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
   4029                             attributes.mFallbackLineSpacing);
   4030                     break;
   4031                 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
   4032                     attributes.mHasLetterSpacing = true;
   4033                     attributes.mLetterSpacing =
   4034                             appearance.getFloat(attr, attributes.mLetterSpacing);
   4035                     break;
   4036                 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
   4037                     attributes.mFontFeatureSettings = appearance.getString(attr);
   4038                     break;
   4039                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
   4040                     attributes.mFontVariationSettings = appearance.getString(attr);
   4041                     break;
   4042                 default:
   4043             }
   4044         }
   4045     }
   4046 
   4047     private void applyTextAppearance(TextAppearanceAttributes attributes) {
   4048         if (attributes.mTextColor != null) {
   4049             setTextColor(attributes.mTextColor);
   4050         }
   4051 
   4052         if (attributes.mTextColorHint != null) {
   4053             setHintTextColor(attributes.mTextColorHint);
   4054         }
   4055 
   4056         if (attributes.mTextColorLink != null) {
   4057             setLinkTextColor(attributes.mTextColorLink);
   4058         }
   4059 
   4060         if (attributes.mTextColorHighlight != 0) {
   4061             setHighlightColor(attributes.mTextColorHighlight);
   4062         }
   4063 
   4064         if (attributes.mTextSize != -1) {
   4065             setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
   4066         }
   4067 
   4068         if (attributes.mTextLocales != null) {
   4069             setTextLocales(attributes.mTextLocales);
   4070         }
   4071 
   4072         if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
   4073             attributes.mFontFamily = null;
   4074         }
   4075         setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
   4076                 attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
   4077 
   4078         if (attributes.mShadowColor != 0) {
   4079             setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
   4080                     attributes.mShadowColor);
   4081         }
   4082 
   4083         if (attributes.mAllCaps) {
   4084             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
   4085         }
   4086 
   4087         if (attributes.mHasElegant) {
   4088             setElegantTextHeight(attributes.mElegant);
   4089         }
   4090 
   4091         if (attributes.mHasFallbackLineSpacing) {
   4092             setFallbackLineSpacing(attributes.mFallbackLineSpacing);
   4093         }
   4094 
   4095         if (attributes.mHasLetterSpacing) {
   4096             setLetterSpacing(attributes.mLetterSpacing);
   4097         }
   4098 
   4099         if (attributes.mFontFeatureSettings != null) {
   4100             setFontFeatureSettings(attributes.mFontFeatureSettings);
   4101         }
   4102 
   4103         if (attributes.mFontVariationSettings != null) {
   4104             setFontVariationSettings(attributes.mFontVariationSettings);
   4105         }
   4106     }
   4107 
   4108     /**
   4109      * Get the default primary {@link Locale} of the text in this TextView. This will always be
   4110      * the first member of {@link #getTextLocales()}.
   4111      * @return the default primary {@link Locale} of the text in this TextView.
   4112      */
   4113     @NonNull
   4114     public Locale getTextLocale() {
   4115         return mTextPaint.getTextLocale();
   4116     }
   4117 
   4118     /**
   4119      * Get the default {@link LocaleList} of the text in this TextView.
   4120      * @return the default {@link LocaleList} of the text in this TextView.
   4121      */
   4122     @NonNull @Size(min = 1)
   4123     public LocaleList getTextLocales() {
   4124         return mTextPaint.getTextLocales();
   4125     }
   4126 
   4127     private void changeListenerLocaleTo(@Nullable Locale locale) {
   4128         if (mListenerChanged) {
   4129             // If a listener has been explicitly set, don't change it. We may break something.
   4130             return;
   4131         }
   4132         // The following null check is not absolutely necessary since all calling points of
   4133         // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
   4134         // here in case others would want to call this method in the future.
   4135         if (mEditor != null) {
   4136             KeyListener listener = mEditor.mKeyListener;
   4137             if (listener instanceof DigitsKeyListener) {
   4138                 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
   4139             } else if (listener instanceof DateKeyListener) {
   4140                 listener = DateKeyListener.getInstance(locale);
   4141             } else if (listener instanceof TimeKeyListener) {
   4142                 listener = TimeKeyListener.getInstance(locale);
   4143             } else if (listener instanceof DateTimeKeyListener) {
   4144                 listener = DateTimeKeyListener.getInstance(locale);
   4145             } else {
   4146                 return;
   4147             }
   4148             final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
   4149             setKeyListenerOnly(listener);
   4150             setInputTypeFromEditor();
   4151             if (wasPasswordType) {
   4152                 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
   4153                 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
   4154                     mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
   4155                 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
   4156                     mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
   4157                 }
   4158             }
   4159         }
   4160     }
   4161 
   4162     /**
   4163      * Set the default {@link Locale} of the text in this TextView to a one-member
   4164      * {@link LocaleList} containing just the given Locale.
   4165      *
   4166      * @param locale the {@link Locale} for drawing text, must not be null.
   4167      *
   4168      * @see #setTextLocales
   4169      */
   4170     public void setTextLocale(@NonNull Locale locale) {
   4171         mLocalesChanged = true;
   4172         mTextPaint.setTextLocale(locale);
   4173         if (mLayout != null) {
   4174             nullLayouts();
   4175             requestLayout();
   4176             invalidate();
   4177         }
   4178     }
   4179 
   4180     /**
   4181      * Set the default {@link LocaleList} of the text in this TextView to the given value.
   4182      *
   4183      * This value is used to choose appropriate typefaces for ambiguous characters (typically used
   4184      * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
   4185      * other aspects of text display, including line breaking.
   4186      *
   4187      * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
   4188      *
   4189      * @see Paint#setTextLocales
   4190      */
   4191     public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
   4192         mLocalesChanged = true;
   4193         mTextPaint.setTextLocales(locales);
   4194         if (mLayout != null) {
   4195             nullLayouts();
   4196             requestLayout();
   4197             invalidate();
   4198         }
   4199     }
   4200 
   4201     @Override
   4202     protected void onConfigurationChanged(Configuration newConfig) {
   4203         super.onConfigurationChanged(newConfig);
   4204         if (!mLocalesChanged) {
   4205             mTextPaint.setTextLocales(LocaleList.getDefault());
   4206             if (mLayout != null) {
   4207                 nullLayouts();
   4208                 requestLayout();
   4209                 invalidate();
   4210             }
   4211         }
   4212     }
   4213 
   4214     /**
   4215      * @return the size (in pixels) of the default text size in this TextView.
   4216      */
   4217     @InspectableProperty
   4218     @ViewDebug.ExportedProperty(category = "text")
   4219     public float getTextSize() {
   4220         return mTextPaint.getTextSize();
   4221     }
   4222 
   4223     /**
   4224      * @return the size (in scaled pixels) of the default text size in this TextView.
   4225      * @hide
   4226      */
   4227     @ViewDebug.ExportedProperty(category = "text")
   4228     public float getScaledTextSize() {
   4229         return mTextPaint.getTextSize() / mTextPaint.density;
   4230     }
   4231 
   4232     /** @hide */
   4233     @ViewDebug.ExportedProperty(category = "text", mapping = {
   4234             @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
   4235             @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
   4236             @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
   4237             @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
   4238     })
   4239     public int getTypefaceStyle() {
   4240         Typeface typeface = mTextPaint.getTypeface();
   4241         return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
   4242     }
   4243 
   4244     /**
   4245      * Set the default text size to the given value, interpreted as "scaled
   4246      * pixel" units.  This size is adjusted based on the current density and
   4247      * user font size preference.
   4248      *
   4249      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
   4250      *
   4251      * @param size The scaled pixel size.
   4252      *
   4253      * @attr ref android.R.styleable#TextView_textSize
   4254      */
   4255     @android.view.RemotableViewMethod
   4256     public void setTextSize(float size) {
   4257         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
   4258     }
   4259 
   4260     /**
   4261      * Set the default text size to a given unit and value. See {@link
   4262      * TypedValue} for the possible dimension units.
   4263      *
   4264      * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
   4265      *
   4266      * @param unit The desired dimension unit.
   4267      * @param size The desired size in the given units.
   4268      *
   4269      * @attr ref android.R.styleable#TextView_textSize
   4270      */
   4271     public void setTextSize(int unit, float size) {
   4272         if (!isAutoSizeEnabled()) {
   4273             setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
   4274         }
   4275     }
   4276 
   4277     private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
   4278         Context c = getContext();
   4279         Resources r;
   4280 
   4281         if (c == null) {
   4282             r = Resources.getSystem();
   4283         } else {
   4284             r = c.getResources();
   4285         }
   4286 
   4287         setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
   4288                 shouldRequestLayout);
   4289     }
   4290 
   4291     @UnsupportedAppUsage
   4292     private void setRawTextSize(float size, boolean shouldRequestLayout) {
   4293         if (size != mTextPaint.getTextSize()) {
   4294             mTextPaint.setTextSize(size);
   4295 
   4296             if (shouldRequestLayout && mLayout != null) {
   4297                 // Do not auto-size right after setting the text size.
   4298                 mNeedsAutoSizeText = false;
   4299                 nullLayouts();
   4300                 requestLayout();
   4301                 invalidate();
   4302             }
   4303         }
   4304     }
   4305 
   4306     /**
   4307      * Gets the extent by which text should be stretched horizontally.
   4308      * This will usually be 1.0.
   4309      * @return The horizontal scale factor.
   4310      */
   4311     @InspectableProperty
   4312     public float getTextScaleX() {
   4313         return mTextPaint.getTextScaleX();
   4314     }
   4315 
   4316     /**
   4317      * Sets the horizontal scale factor for text. The default value
   4318      * is 1.0. Values greater than 1.0 stretch the text wider.
   4319      * Values less than 1.0 make the text narrower. By default, this value is 1.0.
   4320      * @param size The horizontal scale factor.
   4321      * @attr ref android.R.styleable#TextView_textScaleX
   4322      */
   4323     @android.view.RemotableViewMethod
   4324     public void setTextScaleX(float size) {
   4325         if (size != mTextPaint.getTextScaleX()) {
   4326             mUserSetTextScaleX = true;
   4327             mTextPaint.setTextScaleX(size);
   4328 
   4329             if (mLayout != null) {
   4330                 nullLayouts();
   4331                 requestLayout();
   4332                 invalidate();
   4333             }
   4334         }
   4335     }
   4336 
   4337     /**
   4338      * Sets the typeface and style in which the text should be displayed.
   4339      * Note that not all Typeface families actually have bold and italic
   4340      * variants, so you may need to use
   4341      * {@link #setTypeface(Typeface, int)} to get the appearance
   4342      * that you actually want.
   4343      *
   4344      * @see #getTypeface()
   4345      *
   4346      * @attr ref android.R.styleable#TextView_fontFamily
   4347      * @attr ref android.R.styleable#TextView_typeface
   4348      * @attr ref android.R.styleable#TextView_textStyle
   4349      */
   4350     public void setTypeface(@Nullable Typeface tf) {
   4351         if (mTextPaint.getTypeface() != tf) {
   4352             mTextPaint.setTypeface(tf);
   4353 
   4354             if (mLayout != null) {
   4355                 nullLayouts();
   4356                 requestLayout();
   4357                 invalidate();
   4358             }
   4359         }
   4360     }
   4361 
   4362     /**
   4363      * Gets the current {@link Typeface} that is used to style the text.
   4364      * @return The current Typeface.
   4365      *
   4366      * @see #setTypeface(Typeface)
   4367      *
   4368      * @attr ref android.R.styleable#TextView_fontFamily
   4369      * @attr ref android.R.styleable#TextView_typeface
   4370      * @attr ref android.R.styleable#TextView_textStyle
   4371      */
   4372     @InspectableProperty
   4373     public Typeface getTypeface() {
   4374         return mTextPaint.getTypeface();
   4375     }
   4376 
   4377     /**
   4378      * Set the TextView's elegant height metrics flag. This setting selects font
   4379      * variants that have not been compacted to fit Latin-based vertical
   4380      * metrics, and also increases top and bottom bounds to provide more space.
   4381      *
   4382      * @param elegant set the paint's elegant metrics flag.
   4383      *
   4384      * @see #isElegantTextHeight()
   4385      * @see Paint#isElegantTextHeight()
   4386      *
   4387      * @attr ref android.R.styleable#TextView_elegantTextHeight
   4388      */
   4389     public void setElegantTextHeight(boolean elegant) {
   4390         if (elegant != mTextPaint.isElegantTextHeight()) {
   4391             mTextPaint.setElegantTextHeight(elegant);
   4392             if (mLayout != null) {
   4393                 nullLayouts();
   4394                 requestLayout();
   4395                 invalidate();
   4396             }
   4397         }
   4398     }
   4399 
   4400     /**
   4401      * Set whether to respect the ascent and descent of the fallback fonts that are used in
   4402      * displaying the text (which is needed to avoid text from consecutive lines running into
   4403      * each other). If set, fallback fonts that end up getting used can increase the ascent
   4404      * and descent of the lines that they are used on.
   4405      * <p/>
   4406      * It is required to be true if text could be in languages like Burmese or Tibetan where text
   4407      * is typically much taller or deeper than Latin text.
   4408      *
   4409      * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
   4410      *
   4411      * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
   4412      *
   4413      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
   4414      */
   4415     public void setFallbackLineSpacing(boolean enabled) {
   4416         if (mUseFallbackLineSpacing != enabled) {
   4417             mUseFallbackLineSpacing = enabled;
   4418             if (mLayout != null) {
   4419                 nullLayouts();
   4420                 requestLayout();
   4421                 invalidate();
   4422             }
   4423         }
   4424     }
   4425 
   4426     /**
   4427      * @return whether fallback line spacing is enabled, {@code true} by default
   4428      *
   4429      * @see #setFallbackLineSpacing(boolean)
   4430      *
   4431      * @attr ref android.R.styleable#TextView_fallbackLineSpacing
   4432      */
   4433     @InspectableProperty
   4434     public boolean isFallbackLineSpacing() {
   4435         return mUseFallbackLineSpacing;
   4436     }
   4437 
   4438     /**
   4439      * Get the value of the TextView's elegant height metrics flag. This setting selects font
   4440      * variants that have not been compacted to fit Latin-based vertical
   4441      * metrics, and also increases top and bottom bounds to provide more space.
   4442      * @return {@code true} if the elegant height metrics flag is set.
   4443      *
   4444      * @see #setElegantTextHeight(boolean)
   4445      * @see Paint#setElegantTextHeight(boolean)
   4446      */
   4447     @InspectableProperty
   4448     public boolean isElegantTextHeight() {
   4449         return mTextPaint.isElegantTextHeight();
   4450     }
   4451 
   4452     /**
   4453      * Gets the text letter-space value, which determines the spacing between characters.
   4454      * The value returned is in ems. Normally, this value is 0.0.
   4455      * @return The text letter-space value in ems.
   4456      *
   4457      * @see #setLetterSpacing(float)
   4458      * @see Paint#setLetterSpacing
   4459      */
   4460     @InspectableProperty
   4461     public float getLetterSpacing() {
   4462         return mTextPaint.getLetterSpacing();
   4463     }
   4464 
   4465     /**
   4466      * Sets text letter-spacing in em units.  Typical values
   4467      * for slight expansion will be around 0.05.  Negative values tighten text.
   4468      *
   4469      * @see #getLetterSpacing()
   4470      * @see Paint#getLetterSpacing
   4471      *
   4472      * @param letterSpacing A text letter-space value in ems.
   4473      * @attr ref android.R.styleable#TextView_letterSpacing
   4474      */
   4475     @android.view.RemotableViewMethod
   4476     public void setLetterSpacing(float letterSpacing) {
   4477         if (letterSpacing != mTextPaint.getLetterSpacing()) {
   4478             mTextPaint.setLetterSpacing(letterSpacing);
   4479 
   4480             if (mLayout != null) {
   4481                 nullLayouts();
   4482                 requestLayout();
   4483                 invalidate();
   4484             }
   4485         }
   4486     }
   4487 
   4488     /**
   4489      * Returns the font feature settings. The format is the same as the CSS
   4490      * font-feature-settings attribute:
   4491      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
   4492      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
   4493      *
   4494      * @return the currently set font feature settings.  Default is null.
   4495      *
   4496      * @see #setFontFeatureSettings(String)
   4497      * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
   4498      */
   4499     @InspectableProperty
   4500     @Nullable
   4501     public String getFontFeatureSettings() {
   4502         return mTextPaint.getFontFeatureSettings();
   4503     }
   4504 
   4505     /**
   4506      * Returns the font variation settings.
   4507      *
   4508      * @return the currently set font variation settings.  Returns null if no variation is
   4509      * specified.
   4510      *
   4511      * @see #setFontVariationSettings(String)
   4512      * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
   4513      */
   4514     @Nullable
   4515     public String getFontVariationSettings() {
   4516         return mTextPaint.getFontVariationSettings();
   4517     }
   4518 
   4519     /**
   4520      * Sets the break strategy for breaking paragraphs into lines. The default value for
   4521      * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
   4522      * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
   4523      * text "dancing" when being edited.
   4524      * <p/>
   4525      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
   4526      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
   4527      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
   4528      * improves the structure of text layout however has performance impact and requires more time
   4529      * to do the text layout.
   4530      *
   4531      * @attr ref android.R.styleable#TextView_breakStrategy
   4532      * @see #getBreakStrategy()
   4533      * @see #setHyphenationFrequency(int)
   4534      */
   4535     public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
   4536         mBreakStrategy = breakStrategy;
   4537         if (mLayout != null) {
   4538             nullLayouts();
   4539             requestLayout();
   4540             invalidate();
   4541         }
   4542     }
   4543 
   4544     /**
   4545      * Gets the current strategy for breaking paragraphs into lines.
   4546      * @return the current strategy for breaking paragraphs into lines.
   4547      *
   4548      * @attr ref android.R.styleable#TextView_breakStrategy
   4549      * @see #setBreakStrategy(int)
   4550      */
   4551     @InspectableProperty(enumMapping = {
   4552             @EnumEntry(name = "simple", value = Layout.BREAK_STRATEGY_SIMPLE),
   4553             @EnumEntry(name = "high_quality", value = Layout.BREAK_STRATEGY_HIGH_QUALITY),
   4554             @EnumEntry(name = "balanced", value = Layout.BREAK_STRATEGY_BALANCED)
   4555     })
   4556     @Layout.BreakStrategy
   4557     public int getBreakStrategy() {
   4558         return mBreakStrategy;
   4559     }
   4560 
   4561     /**
   4562      * Sets the frequency of automatic hyphenation to use when determining word breaks.
   4563      * The default value for both TextView and {@link EditText} is
   4564      * {@link Layout#HYPHENATION_FREQUENCY_NONE}. Note that the default hyphenation frequency value
   4565      * is set from the theme.
   4566      * <p/>
   4567      * Enabling hyphenation with either using {@link Layout#HYPHENATION_FREQUENCY_NORMAL} or
   4568      * {@link Layout#HYPHENATION_FREQUENCY_FULL} while line breaking is set to one of
   4569      * {@link Layout#BREAK_STRATEGY_BALANCED}, {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}
   4570      * improves the structure of text layout however has performance impact and requires more time
   4571      * to do the text layout.
   4572      * <p/>
   4573      * Note: Before Android Q, in the theme hyphenation frequency is set to
   4574      * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. The default value is changed into
   4575      * {@link Layout#HYPHENATION_FREQUENCY_NONE} on Q.
   4576      *
   4577      * @param hyphenationFrequency the hyphenation frequency to use, one of
   4578      *                             {@link Layout#HYPHENATION_FREQUENCY_NONE},
   4579      *                             {@link Layout#HYPHENATION_FREQUENCY_NORMAL},
   4580      *                             {@link Layout#HYPHENATION_FREQUENCY_FULL}
   4581      * @attr ref android.R.styleable#TextView_hyphenationFrequency
   4582      * @see #getHyphenationFrequency()
   4583      * @see #getBreakStrategy()
   4584      */
   4585     public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
   4586         mHyphenationFrequency = hyphenationFrequency;
   4587         if (mLayout != null) {
   4588             nullLayouts();
   4589             requestLayout();
   4590             invalidate();
   4591         }
   4592     }
   4593 
   4594     /**
   4595      * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
   4596      * @return the current frequency of automatic hyphenation to be used when determining word
   4597      * breaks.
   4598      *
   4599      * @attr ref android.R.styleable#TextView_hyphenationFrequency
   4600      * @see #setHyphenationFrequency(int)
   4601      */
   4602     @InspectableProperty(enumMapping = {
   4603             @EnumEntry(name = "none", value = Layout.HYPHENATION_FREQUENCY_NONE),
   4604             @EnumEntry(name = "normal", value = Layout.HYPHENATION_FREQUENCY_NORMAL),
   4605             @EnumEntry(name = "full", value = Layout.HYPHENATION_FREQUENCY_FULL)
   4606     })
   4607     @Layout.HyphenationFrequency
   4608     public int getHyphenationFrequency() {
   4609         return mHyphenationFrequency;
   4610     }
   4611 
   4612     /**
   4613      * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}.
   4614      *
   4615      * @return a current {@link PrecomputedText.Params}
   4616      * @see PrecomputedText
   4617      */
   4618     public @NonNull PrecomputedText.Params getTextMetricsParams() {
   4619         return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(),
   4620                 mBreakStrategy, mHyphenationFrequency);
   4621     }
   4622 
   4623     /**
   4624      * Apply the text layout parameter.
   4625      *
   4626      * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}.
   4627      * @see PrecomputedText
   4628      */
   4629     public void setTextMetricsParams(@NonNull PrecomputedText.Params params) {
   4630         mTextPaint.set(params.getTextPaint());
   4631         mUserSetTextScaleX = true;
   4632         mTextDir = params.getTextDirection();
   4633         mBreakStrategy = params.getBreakStrategy();
   4634         mHyphenationFrequency = params.getHyphenationFrequency();
   4635         if (mLayout != null) {
   4636             nullLayouts();
   4637             requestLayout();
   4638             invalidate();
   4639         }
   4640     }
   4641 
   4642     /**
   4643      * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
   4644      * last line is too short for justification, the last line will be displayed with the
   4645      * alignment set by {@link android.view.View#setTextAlignment}.
   4646      *
   4647      * @see #getJustificationMode()
   4648      */
   4649     @Layout.JustificationMode
   4650     public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
   4651         mJustificationMode = justificationMode;
   4652         if (mLayout != null) {
   4653             nullLayouts();
   4654             requestLayout();
   4655             invalidate();
   4656         }
   4657     }
   4658 
   4659     /**
   4660      * @return true if currently paragraph justification mode.
   4661      *
   4662      * @see #setJustificationMode(int)
   4663      */
   4664     @InspectableProperty(enumMapping = {
   4665             @EnumEntry(name = "none", value = Layout.JUSTIFICATION_MODE_NONE),
   4666             @EnumEntry(name = "inter_word", value = Layout.JUSTIFICATION_MODE_INTER_WORD)
   4667     })
   4668     public @Layout.JustificationMode int getJustificationMode() {
   4669         return mJustificationMode;
   4670     }
   4671 
   4672     /**
   4673      * Sets font feature settings. The format is the same as the CSS
   4674      * font-feature-settings attribute:
   4675      * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
   4676      *     https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
   4677      *
   4678      * @param fontFeatureSettings font feature settings represented as CSS compatible string
   4679      *
   4680      * @see #getFontFeatureSettings()
   4681      * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
   4682      *
   4683      * @attr ref android.R.styleable#TextView_fontFeatureSettings
   4684      */
   4685     @android.view.RemotableViewMethod
   4686     public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
   4687         if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
   4688             mTextPaint.setFontFeatureSettings(fontFeatureSettings);
   4689 
   4690             if (mLayout != null) {
   4691                 nullLayouts();
   4692                 requestLayout();
   4693                 invalidate();
   4694             }
   4695         }
   4696     }
   4697 
   4698 
   4699     /**
   4700      * Sets TrueType or OpenType font variation settings. The settings string is constructed from
   4701      * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
   4702      * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
   4703      * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
   4704      * are invalid. If a specified axis name is not defined in the font, the settings will be
   4705      * ignored.
   4706      *
   4707      * <p>
   4708      * Examples,
   4709      * <ul>
   4710      * <li>Set font width to 150.
   4711      * <pre>
   4712      * <code>
   4713      *   TextView textView = (TextView) findViewById(R.id.textView);
   4714      *   textView.setFontVariationSettings("'wdth' 150");
   4715      * </code>
   4716      * </pre>
   4717      * </li>
   4718      *
   4719      * <li>Set the font slant to 20 degrees and ask for italic style.
   4720      * <pre>
   4721      * <code>
   4722      *   TextView textView = (TextView) findViewById(R.id.textView);
   4723      *   textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
   4724      * </code>
   4725      * </pre>
   4726      * </p>
   4727      * </li>
   4728      * </ul>
   4729      *
   4730      * @param fontVariationSettings font variation settings. You can pass null or empty string as
   4731      *                              no variation settings.
   4732      * @return true if the given settings is effective to at least one font file underlying this
   4733      *         TextView. This function also returns true for empty settings string. Otherwise
   4734      *         returns false.
   4735      *
   4736      * @throws IllegalArgumentException If given string is not a valid font variation settings
   4737      *                                  format.
   4738      *
   4739      * @see #getFontVariationSettings()
   4740      * @see FontVariationAxis
   4741      *
   4742      * @attr ref android.R.styleable#TextView_fontVariationSettings
   4743      */
   4744     public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
   4745         final String existingSettings = mTextPaint.getFontVariationSettings();
   4746         if (fontVariationSettings == existingSettings
   4747                 || (fontVariationSettings != null
   4748                         && fontVariationSettings.equals(existingSettings))) {
   4749             return true;
   4750         }
   4751         boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
   4752 
   4753         if (effective && mLayout != null) {
   4754             nullLayouts();
   4755             requestLayout();
   4756             invalidate();
   4757         }
   4758         return effective;
   4759     }
   4760 
   4761     /**
   4762      * Sets the text color for all the states (normal, selected,
   4763      * focused) to be this color.
   4764      *
   4765      * @param color A color value in the form 0xAARRGGBB.
   4766      * Do not pass a resource ID. To get a color value from a resource ID, call
   4767      * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
   4768      *
   4769      * @see #setTextColor(ColorStateList)
   4770      * @see #getTextColors()
   4771      *
   4772      * @attr ref android.R.styleable#TextView_textColor
   4773      */
   4774     @android.view.RemotableViewMethod
   4775     public void setTextColor(@ColorInt int color) {
   4776         mTextColor = ColorStateList.valueOf(color);
   4777         updateTextColors();
   4778     }
   4779 
   4780     /**
   4781      * Sets the text color.
   4782      *
   4783      * @see #setTextColor(int)
   4784      * @see #getTextColors()
   4785      * @see #setHintTextColor(ColorStateList)
   4786      * @see #setLinkTextColor(ColorStateList)
   4787      *
   4788      * @attr ref android.R.styleable#TextView_textColor
   4789      */
   4790     @android.view.RemotableViewMethod
   4791     public void setTextColor(ColorStateList colors) {
   4792         if (colors == null) {
   4793             throw new NullPointerException();
   4794         }
   4795 
   4796         mTextColor = colors;
   4797         updateTextColors();
   4798     }
   4799 
   4800     /**
   4801      * Gets the text colors for the different states (normal, selected, focused) of the TextView.
   4802      *
   4803      * @see #setTextColor(ColorStateList)
   4804      * @see #setTextColor(int)
   4805      *
   4806      * @attr ref android.R.styleable#TextView_textColor
   4807      */
   4808     @InspectableProperty(name = "textColor")
   4809     public final ColorStateList getTextColors() {
   4810         return mTextColor;
   4811     }
   4812 
   4813     /**
   4814      * Return the current color selected for normal text.
   4815      *
   4816      * @return Returns the current text color.
   4817      */
   4818     @ColorInt
   4819     public final int getCurrentTextColor() {
   4820         return mCurTextColor;
   4821     }
   4822 
   4823     /**
   4824      * Sets the color used to display the selection highlight.
   4825      *
   4826      * @attr ref android.R.styleable#TextView_textColorHighlight
   4827      */
   4828     @android.view.RemotableViewMethod
   4829     public void setHighlightColor(@ColorInt int color) {
   4830         if (mHighlightColor != color) {
   4831             mHighlightColor = color;
   4832             invalidate();
   4833         }
   4834     }
   4835 
   4836     /**
   4837      * @return the color used to display the selection highlight
   4838      *
   4839      * @see #setHighlightColor(int)
   4840      *
   4841      * @attr ref android.R.styleable#TextView_textColorHighlight
   4842      */
   4843     @InspectableProperty(name = "textColorHighlight")
   4844     @ColorInt
   4845     public int getHighlightColor() {
   4846         return mHighlightColor;
   4847     }
   4848 
   4849     /**
   4850      * Sets whether the soft input method will be made visible when this
   4851      * TextView gets focused. The default is true.
   4852      */
   4853     @android.view.RemotableViewMethod
   4854     public final void setShowSoftInputOnFocus(boolean show) {
   4855         createEditorIfNeeded();
   4856         mEditor.mShowSoftInputOnFocus = show;
   4857     }
   4858 
   4859     /**
   4860      * Returns whether the soft input method will be made visible when this
   4861      * TextView gets focused. The default is true.
   4862      */
   4863     public final boolean getShowSoftInputOnFocus() {
   4864         // When there is no Editor, return default true value
   4865         return mEditor == null || mEditor.mShowSoftInputOnFocus;
   4866     }
   4867 
   4868     /**
   4869      * Gives the text a shadow of the specified blur radius and color, the specified
   4870      * distance from its drawn position.
   4871      * <p>
   4872      * The text shadow produced does not interact with the properties on view
   4873      * that are responsible for real time shadows,
   4874      * {@link View#getElevation() elevation} and
   4875      * {@link View#getTranslationZ() translationZ}.
   4876      *
   4877      * @see Paint#setShadowLayer(float, float, float, int)
   4878      *
   4879      * @attr ref android.R.styleable#TextView_shadowColor
   4880      * @attr ref android.R.styleable#TextView_shadowDx
   4881      * @attr ref android.R.styleable#TextView_shadowDy
   4882      * @attr ref android.R.styleable#TextView_shadowRadius
   4883      */
   4884     public void setShadowLayer(float radius, float dx, float dy, int color) {
   4885         mTextPaint.setShadowLayer(radius, dx, dy, color);
   4886 
   4887         mShadowRadius = radius;
   4888         mShadowDx = dx;
   4889         mShadowDy = dy;
   4890         mShadowColor = color;
   4891 
   4892         // Will change text clip region
   4893         if (mEditor != null) {
   4894             mEditor.invalidateTextDisplayList();
   4895             mEditor.invalidateHandlesAndActionMode();
   4896         }
   4897         invalidate();
   4898     }
   4899 
   4900     /**
   4901      * Gets the radius of the shadow layer.
   4902      *
   4903      * @return the radius of the shadow layer. If 0, the shadow layer is not visible
   4904      *
   4905      * @see #setShadowLayer(float, float, float, int)
   4906      *
   4907      * @attr ref android.R.styleable#TextView_shadowRadius
   4908      */
   4909     @InspectableProperty
   4910     public float getShadowRadius() {
   4911         return mShadowRadius;
   4912     }
   4913 
   4914     /**
   4915      * @return the horizontal offset of the shadow layer
   4916      *
   4917      * @see #setShadowLayer(float, float, float, int)
   4918      *
   4919      * @attr ref android.R.styleable#TextView_shadowDx
   4920      */
   4921     @InspectableProperty
   4922     public float getShadowDx() {
   4923         return mShadowDx;
   4924     }
   4925 
   4926     /**
   4927      * Gets the vertical offset of the shadow layer.
   4928      * @return The vertical offset of the shadow layer.
   4929      *
   4930      * @see #setShadowLayer(float, float, float, int)
   4931      *
   4932      * @attr ref android.R.styleable#TextView_shadowDy
   4933      */
   4934     @InspectableProperty
   4935     public float getShadowDy() {
   4936         return mShadowDy;
   4937     }
   4938 
   4939     /**
   4940      * Gets the color of the shadow layer.
   4941      * @return the color of the shadow layer
   4942      *
   4943      * @see #setShadowLayer(float, float, float, int)
   4944      *
   4945      * @attr ref android.R.styleable#TextView_shadowColor
   4946      */
   4947     @InspectableProperty
   4948     @ColorInt
   4949     public int getShadowColor() {
   4950         return mShadowColor;
   4951     }
   4952 
   4953     /**
   4954      * Gets the {@link TextPaint} used for the text.
   4955      * Use this only to consult the Paint's properties and not to change them.
   4956      * @return The base paint used for the text.
   4957      */
   4958     public TextPaint getPaint() {
   4959         return mTextPaint;
   4960     }
   4961 
   4962     /**
   4963      * Sets the autolink mask of the text.  See {@link
   4964      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
   4965      * possible values.
   4966      *
   4967      * <p class="note"><b>Note:</b>
   4968      * {@link android.text.util.Linkify#MAP_ADDRESSES Linkify.MAP_ADDRESSES}
   4969      * is deprecated and should be avoided; see its documentation.
   4970      *
   4971      * @attr ref android.R.styleable#TextView_autoLink
   4972      */
   4973     @android.view.RemotableViewMethod
   4974     public final void setAutoLinkMask(int mask) {
   4975         mAutoLinkMask = mask;
   4976     }
   4977 
   4978     /**
   4979      * Sets whether the movement method will automatically be set to
   4980      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   4981      * set to nonzero and links are detected in {@link #setText}.
   4982      * The default is true.
   4983      *
   4984      * @attr ref android.R.styleable#TextView_linksClickable
   4985      */
   4986     @android.view.RemotableViewMethod
   4987     public final void setLinksClickable(boolean whether) {
   4988         mLinksClickable = whether;
   4989     }
   4990 
   4991     /**
   4992      * Returns whether the movement method will automatically be set to
   4993      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
   4994      * set to nonzero and links are detected in {@link #setText}.
   4995      * The default is true.
   4996      *
   4997      * @attr ref android.R.styleable#TextView_linksClickable
   4998      */
   4999     @InspectableProperty
   5000     public final boolean getLinksClickable() {
   5001         return mLinksClickable;
   5002     }
   5003 
   5004     /**
   5005      * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
   5006      * (by {@link Linkify} or otherwise) if any.  You can call
   5007      * {@link URLSpan#getURL} on them to find where they link to
   5008      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
   5009      * to find the region of the text they are attached to.
   5010      */
   5011     public URLSpan[] getUrls() {
   5012         if (mText instanceof Spanned) {
   5013             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
   5014         } else {
   5015             return new URLSpan[0];
   5016         }
   5017     }
   5018 
   5019     /**
   5020      * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
   5021      * TextView.
   5022      *
   5023      * @see #setHintTextColor(ColorStateList)
   5024      * @see #getHintTextColors()
   5025      * @see #setTextColor(int)
   5026      *
   5027      * @attr ref android.R.styleable#TextView_textColorHint
   5028      */
   5029     @android.view.RemotableViewMethod
   5030     public final void setHintTextColor(@ColorInt int color) {
   5031         mHintTextColor = ColorStateList.valueOf(color);
   5032         updateTextColors();
   5033     }
   5034 
   5035     /**
   5036      * Sets the color of the hint text.
   5037      *
   5038      * @see #getHintTextColors()
   5039      * @see #setHintTextColor(int)
   5040      * @see #setTextColor(ColorStateList)
   5041      * @see #setLinkTextColor(ColorStateList)
   5042      *
   5043      * @attr ref android.R.styleable#TextView_textColorHint
   5044      */
   5045     public final void setHintTextColor(ColorStateList colors) {
   5046         mHintTextColor = colors;
   5047         updateTextColors();
   5048     }
   5049 
   5050     /**
   5051      * @return the color of the hint text, for the different states of this TextView.
   5052      *
   5053      * @see #setHintTextColor(ColorStateList)
   5054      * @see #setHintTextColor(int)
   5055      * @see #setTextColor(ColorStateList)
   5056      * @see #setLinkTextColor(ColorStateList)
   5057      *
   5058      * @attr ref android.R.styleable#TextView_textColorHint
   5059      */
   5060     @InspectableProperty(name = "textColorHint")
   5061     public final ColorStateList getHintTextColors() {
   5062         return mHintTextColor;
   5063     }
   5064 
   5065     /**
   5066      * <p>Return the current color selected to paint the hint text.</p>
   5067      *
   5068      * @return Returns the current hint text color.
   5069      */
   5070     @ColorInt
   5071     public final int getCurrentHintTextColor() {
   5072         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
   5073     }
   5074 
   5075     /**
   5076      * Sets the color of links in the text.
   5077      *
   5078      * @see #setLinkTextColor(ColorStateList)
   5079      * @see #getLinkTextColors()
   5080      *
   5081      * @attr ref android.R.styleable#TextView_textColorLink
   5082      */
   5083     @android.view.RemotableViewMethod
   5084     public final void setLinkTextColor(@ColorInt int color) {
   5085         mLinkTextColor = ColorStateList.valueOf(color);
   5086         updateTextColors();
   5087     }
   5088 
   5089     /**
   5090      * Sets the color of links in the text.
   5091      *
   5092      * @see #setLinkTextColor(int)
   5093      * @see #getLinkTextColors()
   5094      * @see #setTextColor(ColorStateList)
   5095      * @see #setHintTextColor(ColorStateList)
   5096      *
   5097      * @attr ref android.R.styleable#TextView_textColorLink
   5098      */
   5099     public final void setLinkTextColor(ColorStateList colors) {
   5100         mLinkTextColor = colors;
   5101         updateTextColors();
   5102     }
   5103 
   5104     /**
   5105      * @return the list of colors used to paint the links in the text, for the different states of
   5106      * this TextView
   5107      *
   5108      * @see #setLinkTextColor(ColorStateList)
   5109      * @see #setLinkTextColor(int)
   5110      *
   5111      * @attr ref android.R.styleable#TextView_textColorLink
   5112      */
   5113     @InspectableProperty(name = "textColorLink")
   5114     public final ColorStateList getLinkTextColors() {
   5115         return mLinkTextColor;
   5116     }
   5117 
   5118     /**
   5119      * Sets the horizontal alignment of the text and the
   5120      * vertical gravity that will be used when there is extra space
   5121      * in the TextView beyond what is required for the text itself.
   5122      *
   5123      * @see android.view.Gravity
   5124      * @attr ref android.R.styleable#TextView_gravity
   5125      */
   5126     public void setGravity(int gravity) {
   5127         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
   5128             gravity |= Gravity.START;
   5129         }
   5130         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
   5131             gravity |= Gravity.TOP;
   5132         }
   5133 
   5134         boolean newLayout = false;
   5135 
   5136         if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
   5137                 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
   5138             newLayout = true;
   5139         }
   5140 
   5141         if (gravity != mGravity) {
   5142             invalidate();
   5143         }
   5144 
   5145         mGravity = gravity;
   5146 
   5147         if (mLayout != null && newLayout) {
   5148             // XXX this is heavy-handed because no actual content changes.
   5149             int want = mLayout.getWidth();
   5150             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   5151 
   5152             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   5153                     mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
   5154         }
   5155     }
   5156 
   5157     /**
   5158      * Returns the horizontal and vertical alignment of this TextView.
   5159      *
   5160      * @see android.view.Gravity
   5161      * @attr ref android.R.styleable#TextView_gravity
   5162      */
   5163     @InspectableProperty(valueType = InspectableProperty.ValueType.GRAVITY)
   5164     public int getGravity() {
   5165         return mGravity;
   5166     }
   5167 
   5168     /**
   5169      * Gets the flags on the Paint being used to display the text.
   5170      * @return The flags on the Paint being used to display the text.
   5171      * @see Paint#getFlags
   5172      */
   5173     public int getPaintFlags() {
   5174         return mTextPaint.getFlags();
   5175     }
   5176 
   5177     /**
   5178      * Sets flags on the Paint being used to display the text and
   5179      * reflows the text if they are different from the old flags.
   5180      * @see Paint#setFlags
   5181      */
   5182     @android.view.RemotableViewMethod
   5183     public void setPaintFlags(int flags) {
   5184         if (mTextPaint.getFlags() != flags) {
   5185             mTextPaint.setFlags(flags);
   5186 
   5187             if (mLayout != null) {
   5188                 nullLayouts();
   5189                 requestLayout();
   5190                 invalidate();
   5191             }
   5192         }
   5193     }
   5194 
   5195     /**
   5196      * Sets whether the text should be allowed to be wider than the
   5197      * View is.  If false, it will be wrapped to the width of the View.
   5198      *
   5199      * @attr ref android.R.styleable#TextView_scrollHorizontally
   5200      */
   5201     public void setHorizontallyScrolling(boolean whether) {
   5202         if (mHorizontallyScrolling != whether) {
   5203             mHorizontallyScrolling = whether;
   5204 
   5205             if (mLayout != null) {
   5206                 nullLayouts();
   5207                 requestLayout();
   5208                 invalidate();
   5209             }
   5210         }
   5211     }
   5212 
   5213     /**
   5214      * Returns whether the text is allowed to be wider than the View.
   5215      * If false, the text will be wrapped to the width of the View.
   5216      *
   5217      * @attr ref android.R.styleable#TextView_scrollHorizontally
   5218      * @see #setHorizontallyScrolling(boolean)
   5219      */
   5220     @InspectableProperty(name = "scrollHorizontally")
   5221     public final boolean isHorizontallyScrollable() {
   5222         return mHorizontallyScrolling;
   5223     }
   5224 
   5225     /**
   5226      * Returns whether the text is allowed to be wider than the View.
   5227      * If false, the text will be wrapped to the width of the View.
   5228      *
   5229      * @attr ref android.R.styleable#TextView_scrollHorizontally
   5230      * @hide
   5231      */
   5232     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
   5233     public boolean getHorizontallyScrolling() {
   5234         return mHorizontallyScrolling;
   5235     }
   5236 
   5237     /**
   5238      * Sets the height of the TextView to be at least {@code minLines} tall.
   5239      * <p>
   5240      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5241      * exact height. Setting this value overrides other previous minimum height configurations such
   5242      * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
   5243      * this value to 1.
   5244      *
   5245      * @param minLines the minimum height of TextView in terms of number of lines
   5246      *
   5247      * @see #getMinLines()
   5248      * @see #setLines(int)
   5249      *
   5250      * @attr ref android.R.styleable#TextView_minLines
   5251      */
   5252     @android.view.RemotableViewMethod
   5253     public void setMinLines(int minLines) {
   5254         mMinimum = minLines;
   5255         mMinMode = LINES;
   5256 
   5257         requestLayout();
   5258         invalidate();
   5259     }
   5260 
   5261     /**
   5262      * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
   5263      * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
   5264      *
   5265      * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
   5266      *         height is not defined in lines
   5267      *
   5268      * @see #setMinLines(int)
   5269      * @see #setLines(int)
   5270      *
   5271      * @attr ref android.R.styleable#TextView_minLines
   5272      */
   5273     @InspectableProperty
   5274     public int getMinLines() {
   5275         return mMinMode == LINES ? mMinimum : -1;
   5276     }
   5277 
   5278     /**
   5279      * Sets the height of the TextView to be at least {@code minPixels} tall.
   5280      * <p>
   5281      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5282      * exact height. Setting this value overrides previous minimum height configurations such as
   5283      * {@link #setMinLines(int)} or {@link #setLines(int)}.
   5284      * <p>
   5285      * The value given here is different than {@link #setMinimumHeight(int)}. Between
   5286      * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
   5287      * used to decide the final height.
   5288      *
   5289      * @param minPixels the minimum height of TextView in terms of pixels
   5290      *
   5291      * @see #getMinHeight()
   5292      * @see #setHeight(int)
   5293      *
   5294      * @attr ref android.R.styleable#TextView_minHeight
   5295      */
   5296     @android.view.RemotableViewMethod
   5297     public void setMinHeight(int minPixels) {
   5298         mMinimum = minPixels;
   5299         mMinMode = PIXELS;
   5300 
   5301         requestLayout();
   5302         invalidate();
   5303     }
   5304 
   5305     /**
   5306      * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
   5307      * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
   5308      *
   5309      * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
   5310      *         defined in pixels
   5311      *
   5312      * @see #setMinHeight(int)
   5313      * @see #setHeight(int)
   5314      *
   5315      * @attr ref android.R.styleable#TextView_minHeight
   5316      */
   5317     public int getMinHeight() {
   5318         return mMinMode == PIXELS ? mMinimum : -1;
   5319     }
   5320 
   5321     /**
   5322      * Sets the height of the TextView to be at most {@code maxLines} tall.
   5323      * <p>
   5324      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5325      * exact height. Setting this value overrides previous maximum height configurations such as
   5326      * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
   5327      *
   5328      * @param maxLines the maximum height of TextView in terms of number of lines
   5329      *
   5330      * @see #getMaxLines()
   5331      * @see #setLines(int)
   5332      *
   5333      * @attr ref android.R.styleable#TextView_maxLines
   5334      */
   5335     @android.view.RemotableViewMethod
   5336     public void setMaxLines(int maxLines) {
   5337         mMaximum = maxLines;
   5338         mMaxMode = LINES;
   5339 
   5340         requestLayout();
   5341         invalidate();
   5342     }
   5343 
   5344     /**
   5345      * Returns the maximum height of TextView in terms of number of lines or -1 if the
   5346      * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
   5347      *
   5348      * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
   5349      *         is not defined in lines.
   5350      *
   5351      * @see #setMaxLines(int)
   5352      * @see #setLines(int)
   5353      *
   5354      * @attr ref android.R.styleable#TextView_maxLines
   5355      */
   5356     @InspectableProperty
   5357     public int getMaxLines() {
   5358         return mMaxMode == LINES ? mMaximum : -1;
   5359     }
   5360 
   5361     /**
   5362      * Sets the height of the TextView to be at most {@code maxPixels} tall.
   5363      * <p>
   5364      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5365      * exact height. Setting this value overrides previous maximum height configurations such as
   5366      * {@link #setMaxLines(int)} or {@link #setLines(int)}.
   5367      *
   5368      * @param maxPixels the maximum height of TextView in terms of pixels
   5369      *
   5370      * @see #getMaxHeight()
   5371      * @see #setHeight(int)
   5372      *
   5373      * @attr ref android.R.styleable#TextView_maxHeight
   5374      */
   5375     @android.view.RemotableViewMethod
   5376     public void setMaxHeight(int maxPixels) {
   5377         mMaximum = maxPixels;
   5378         mMaxMode = PIXELS;
   5379 
   5380         requestLayout();
   5381         invalidate();
   5382     }
   5383 
   5384     /**
   5385      * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
   5386      * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
   5387      *
   5388      * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
   5389      *         is not defined in pixels
   5390      *
   5391      * @see #setMaxHeight(int)
   5392      * @see #setHeight(int)
   5393      *
   5394      * @attr ref android.R.styleable#TextView_maxHeight
   5395      */
   5396     @InspectableProperty
   5397     public int getMaxHeight() {
   5398         return mMaxMode == PIXELS ? mMaximum : -1;
   5399     }
   5400 
   5401     /**
   5402      * Sets the height of the TextView to be exactly {@code lines} tall.
   5403      * <p>
   5404      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5405      * exact height. Setting this value overrides previous minimum/maximum height configurations
   5406      * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
   5407      * set this value to 1.
   5408      *
   5409      * @param lines the exact height of the TextView in terms of lines
   5410      *
   5411      * @see #setHeight(int)
   5412      *
   5413      * @attr ref android.R.styleable#TextView_lines
   5414      */
   5415     @android.view.RemotableViewMethod
   5416     public void setLines(int lines) {
   5417         mMaximum = mMinimum = lines;
   5418         mMaxMode = mMinMode = LINES;
   5419 
   5420         requestLayout();
   5421         invalidate();
   5422     }
   5423 
   5424     /**
   5425      * Sets the height of the TextView to be exactly <code>pixels</code> tall.
   5426      * <p>
   5427      * This value is used for height calculation if LayoutParams does not force TextView to have an
   5428      * exact height. Setting this value overrides previous minimum/maximum height configurations
   5429      * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
   5430      *
   5431      * @param pixels the exact height of the TextView in terms of pixels
   5432      *
   5433      * @see #setLines(int)
   5434      *
   5435      * @attr ref android.R.styleable#TextView_height
   5436      */
   5437     @android.view.RemotableViewMethod
   5438     public void setHeight(int pixels) {
   5439         mMaximum = mMinimum = pixels;
   5440         mMaxMode = mMinMode = PIXELS;
   5441 
   5442         requestLayout();
   5443         invalidate();
   5444     }
   5445 
   5446     /**
   5447      * Sets the width of the TextView to be at least {@code minEms} wide.
   5448      * <p>
   5449      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5450      * exact width. Setting this value overrides previous minimum width configurations such as
   5451      * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
   5452      *
   5453      * @param minEms the minimum width of TextView in terms of ems
   5454      *
   5455      * @see #getMinEms()
   5456      * @see #setEms(int)
   5457      *
   5458      * @attr ref android.R.styleable#TextView_minEms
   5459      */
   5460     @android.view.RemotableViewMethod
   5461     public void setMinEms(int minEms) {
   5462         mMinWidth = minEms;
   5463         mMinWidthMode = EMS;
   5464 
   5465         requestLayout();
   5466         invalidate();
   5467     }
   5468 
   5469     /**
   5470      * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
   5471      * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
   5472      *
   5473      * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
   5474      *         defined in ems
   5475      *
   5476      * @see #setMinEms(int)
   5477      * @see #setEms(int)
   5478      *
   5479      * @attr ref android.R.styleable#TextView_minEms
   5480      */
   5481     @InspectableProperty
   5482     public int getMinEms() {
   5483         return mMinWidthMode == EMS ? mMinWidth : -1;
   5484     }
   5485 
   5486     /**
   5487      * Sets the width of the TextView to be at least {@code minPixels} wide.
   5488      * <p>
   5489      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5490      * exact width. Setting this value overrides previous minimum width configurations such as
   5491      * {@link #setMinEms(int)} or {@link #setEms(int)}.
   5492      * <p>
   5493      * The value given here is different than {@link #setMinimumWidth(int)}. Between
   5494      * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
   5495      * to decide the final width.
   5496      *
   5497      * @param minPixels the minimum width of TextView in terms of pixels
   5498      *
   5499      * @see #getMinWidth()
   5500      * @see #setWidth(int)
   5501      *
   5502      * @attr ref android.R.styleable#TextView_minWidth
   5503      */
   5504     @android.view.RemotableViewMethod
   5505     public void setMinWidth(int minPixels) {
   5506         mMinWidth = minPixels;
   5507         mMinWidthMode = PIXELS;
   5508 
   5509         requestLayout();
   5510         invalidate();
   5511     }
   5512 
   5513     /**
   5514      * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
   5515      * using {@link #setMinEms(int)} or {@link #setEms(int)}.
   5516      *
   5517      * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
   5518      *         defined in pixels
   5519      *
   5520      * @see #setMinWidth(int)
   5521      * @see #setWidth(int)
   5522      *
   5523      * @attr ref android.R.styleable#TextView_minWidth
   5524      */
   5525     @InspectableProperty
   5526     public int getMinWidth() {
   5527         return mMinWidthMode == PIXELS ? mMinWidth : -1;
   5528     }
   5529 
   5530     /**
   5531      * Sets the width of the TextView to be at most {@code maxEms} wide.
   5532      * <p>
   5533      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5534      * exact width. Setting this value overrides previous maximum width configurations such as
   5535      * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
   5536      *
   5537      * @param maxEms the maximum width of TextView in terms of ems
   5538      *
   5539      * @see #getMaxEms()
   5540      * @see #setEms(int)
   5541      *
   5542      * @attr ref android.R.styleable#TextView_maxEms
   5543      */
   5544     @android.view.RemotableViewMethod
   5545     public void setMaxEms(int maxEms) {
   5546         mMaxWidth = maxEms;
   5547         mMaxWidthMode = EMS;
   5548 
   5549         requestLayout();
   5550         invalidate();
   5551     }
   5552 
   5553     /**
   5554      * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
   5555      * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
   5556      *
   5557      * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
   5558      *         defined in ems
   5559      *
   5560      * @see #setMaxEms(int)
   5561      * @see #setEms(int)
   5562      *
   5563      * @attr ref android.R.styleable#TextView_maxEms
   5564      */
   5565     @InspectableProperty
   5566     public int getMaxEms() {
   5567         return mMaxWidthMode == EMS ? mMaxWidth : -1;
   5568     }
   5569 
   5570     /**
   5571      * Sets the width of the TextView to be at most {@code maxPixels} wide.
   5572      * <p>
   5573      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5574      * exact width. Setting this value overrides previous maximum width configurations such as
   5575      * {@link #setMaxEms(int)} or {@link #setEms(int)}.
   5576      *
   5577      * @param maxPixels the maximum width of TextView in terms of pixels
   5578      *
   5579      * @see #getMaxWidth()
   5580      * @see #setWidth(int)
   5581      *
   5582      * @attr ref android.R.styleable#TextView_maxWidth
   5583      */
   5584     @android.view.RemotableViewMethod
   5585     public void setMaxWidth(int maxPixels) {
   5586         mMaxWidth = maxPixels;
   5587         mMaxWidthMode = PIXELS;
   5588 
   5589         requestLayout();
   5590         invalidate();
   5591     }
   5592 
   5593     /**
   5594      * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
   5595      * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
   5596      *
   5597      * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
   5598      *         defined in pixels
   5599      *
   5600      * @see #setMaxWidth(int)
   5601      * @see #setWidth(int)
   5602      *
   5603      * @attr ref android.R.styleable#TextView_maxWidth
   5604      */
   5605     @InspectableProperty
   5606     public int getMaxWidth() {
   5607         return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
   5608     }
   5609 
   5610     /**
   5611      * Sets the width of the TextView to be exactly {@code ems} wide.
   5612      *
   5613      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5614      * exact width. Setting this value overrides previous minimum/maximum configurations such as
   5615      * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
   5616      *
   5617      * @param ems the exact width of the TextView in terms of ems
   5618      *
   5619      * @see #setWidth(int)
   5620      *
   5621      * @attr ref android.R.styleable#TextView_ems
   5622      */
   5623     @android.view.RemotableViewMethod
   5624     public void setEms(int ems) {
   5625         mMaxWidth = mMinWidth = ems;
   5626         mMaxWidthMode = mMinWidthMode = EMS;
   5627 
   5628         requestLayout();
   5629         invalidate();
   5630     }
   5631 
   5632     /**
   5633      * Sets the width of the TextView to be exactly {@code pixels} wide.
   5634      * <p>
   5635      * This value is used for width calculation if LayoutParams does not force TextView to have an
   5636      * exact width. Setting this value overrides previous minimum/maximum width configurations
   5637      * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
   5638      *
   5639      * @param pixels the exact width of the TextView in terms of pixels
   5640      *
   5641      * @see #setEms(int)
   5642      *
   5643      * @attr ref android.R.styleable#TextView_width
   5644      */
   5645     @android.view.RemotableViewMethod
   5646     public void setWidth(int pixels) {
   5647         mMaxWidth = mMinWidth = pixels;
   5648         mMaxWidthMode = mMinWidthMode = PIXELS;
   5649 
   5650         requestLayout();
   5651         invalidate();
   5652     }
   5653 
   5654     /**
   5655      * Sets line spacing for this TextView.  Each line other than the last line will have its height
   5656      * multiplied by {@code mult} and have {@code add} added to it.
   5657      *
   5658      * @param add The value in pixels that should be added to each line other than the last line.
   5659      *            This will be applied after the multiplier
   5660      * @param mult The value by which each line height other than the last line will be multiplied
   5661      *             by
   5662      *
   5663      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   5664      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   5665      */
   5666     public void setLineSpacing(float add, float mult) {
   5667         if (mSpacingAdd != add || mSpacingMult != mult) {
   5668             mSpacingAdd = add;
   5669             mSpacingMult = mult;
   5670 
   5671             if (mLayout != null) {
   5672                 nullLayouts();
   5673                 requestLayout();
   5674                 invalidate();
   5675             }
   5676         }
   5677     }
   5678 
   5679     /**
   5680      * Gets the line spacing multiplier
   5681      *
   5682      * @return the value by which each line's height is multiplied to get its actual height.
   5683      *
   5684      * @see #setLineSpacing(float, float)
   5685      * @see #getLineSpacingExtra()
   5686      *
   5687      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
   5688      */
   5689     @InspectableProperty
   5690     public float getLineSpacingMultiplier() {
   5691         return mSpacingMult;
   5692     }
   5693 
   5694     /**
   5695      * Gets the line spacing extra space
   5696      *
   5697      * @return the extra space that is added to the height of each lines of this TextView.
   5698      *
   5699      * @see #setLineSpacing(float, float)
   5700      * @see #getLineSpacingMultiplier()
   5701      *
   5702      * @attr ref android.R.styleable#TextView_lineSpacingExtra
   5703      */
   5704     @InspectableProperty
   5705     public float getLineSpacingExtra() {
   5706         return mSpacingAdd;
   5707     }
   5708 
   5709     /**
   5710      * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
   5711      * between subsequent baselines in the TextView.
   5712      *
   5713      * @param lineHeight the line height in pixels
   5714      *
   5715      * @see #setLineSpacing(float, float)
   5716      * @see #getLineSpacingExtra()
   5717      *
   5718      * @attr ref android.R.styleable#TextView_lineHeight
   5719      */
   5720     public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
   5721         Preconditions.checkArgumentNonnegative(lineHeight);
   5722 
   5723         final int fontHeight = getPaint().getFontMetricsInt(null);
   5724         // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
   5725         if (lineHeight != fontHeight) {
   5726             // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
   5727             setLineSpacing(lineHeight - fontHeight, 1f);
   5728         }
   5729     }
   5730 
   5731     /**
   5732      * Convenience method to append the specified text to the TextView's
   5733      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
   5734      * if it was not already editable.
   5735      *
   5736      * @param text text to be appended to the already displayed text
   5737      */
   5738     public final void append(CharSequence text) {
   5739         append(text, 0, text.length());
   5740     }
   5741 
   5742     /**
   5743      * Convenience method to append the specified text slice to the TextView's
   5744      * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
   5745      * if it was not already editable.
   5746      *
   5747      * @param text text to be appended to the already displayed text
   5748      * @param start the index of the first character in the {@code text}
   5749      * @param end the index of the character following the last character in the {@code text}
   5750      *
   5751      * @see Appendable#append(CharSequence, int, int)
   5752      */
   5753     public void append(CharSequence text, int start, int end) {
   5754         if (!(mText instanceof Editable)) {
   5755             setText(mText, BufferType.EDITABLE);
   5756         }
   5757 
   5758         ((Editable) mText).append(text, start, end);
   5759 
   5760         if (mAutoLinkMask != 0) {
   5761             boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
   5762             // Do not change the movement method for text that support text selection as it
   5763             // would prevent an arbitrary cursor displacement.
   5764             if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
   5765                 setMovementMethod(LinkMovementMethod.getInstance());
   5766             }
   5767         }
   5768     }
   5769 
   5770     private void updateTextColors() {
   5771         boolean inval = false;
   5772         final int[] drawableState = getDrawableState();
   5773         int color = mTextColor.getColorForState(drawableState, 0);
   5774         if (color != mCurTextColor) {
   5775             mCurTextColor = color;
   5776             inval = true;
   5777         }
   5778         if (mLinkTextColor != null) {
   5779             color = mLinkTextColor.getColorForState(drawableState, 0);
   5780             if (color != mTextPaint.linkColor) {
   5781                 mTextPaint.linkColor = color;
   5782                 inval = true;
   5783             }
   5784         }
   5785         if (mHintTextColor != null) {
   5786             color = mHintTextColor.getColorForState(drawableState, 0);
   5787             if (color != mCurHintTextColor) {
   5788                 mCurHintTextColor = color;
   5789                 if (mText.length() == 0) {
   5790                     inval = true;
   5791                 }
   5792             }
   5793         }
   5794         if (inval) {
   5795             // Text needs to be redrawn with the new color
   5796             if (mEditor != null) mEditor.invalidateTextDisplayList();
   5797             invalidate();
   5798         }
   5799     }
   5800 
   5801     @Override
   5802     protected void drawableStateChanged() {
   5803         super.drawableStateChanged();
   5804 
   5805         if (mTextColor != null && mTextColor.isStateful()
   5806                 || (mHintTextColor != null && mHintTextColor.isStateful())
   5807                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
   5808             updateTextColors();
   5809         }
   5810 
   5811         if (mDrawables != null) {
   5812             final int[] state = getDrawableState();
   5813             for (Drawable dr : mDrawables.mShowing) {
   5814                 if (dr != null && dr.isStateful() && dr.setState(state)) {
   5815                     invalidateDrawable(dr);
   5816                 }
   5817             }
   5818         }
   5819     }
   5820 
   5821     @Override
   5822     public void drawableHotspotChanged(float x, float y) {
   5823         super.drawableHotspotChanged(x, y);
   5824 
   5825         if (mDrawables != null) {
   5826             for (Drawable dr : mDrawables.mShowing) {
   5827                 if (dr != null) {
   5828                     dr.setHotspot(x, y);
   5829                 }
   5830             }
   5831         }
   5832     }
   5833 
   5834     @Override
   5835     public Parcelable onSaveInstanceState() {
   5836         Parcelable superState = super.onSaveInstanceState();
   5837 
   5838         // Save state if we are forced to
   5839         final boolean freezesText = getFreezesText();
   5840         boolean hasSelection = false;
   5841         int start = -1;
   5842         int end = -1;
   5843 
   5844         if (mText != null) {
   5845             start = getSelectionStart();
   5846             end = getSelectionEnd();
   5847             if (start >= 0 || end >= 0) {
   5848                 // Or save state if there is a selection
   5849                 hasSelection = true;
   5850             }
   5851         }
   5852 
   5853         if (freezesText || hasSelection) {
   5854             SavedState ss = new SavedState(superState);
   5855 
   5856             if (freezesText) {
   5857                 if (mText instanceof Spanned) {
   5858                     final Spannable sp = new SpannableStringBuilder(mText);
   5859 
   5860                     if (mEditor != null) {
   5861                         removeMisspelledSpans(sp);
   5862                         sp.removeSpan(mEditor.mSuggestionRangeSpan);
   5863                     }
   5864 
   5865                     ss.text = sp;
   5866                 } else {
   5867                     ss.text = mText.toString();
   5868                 }
   5869             }
   5870 
   5871             if (hasSelection) {
   5872                 // XXX Should also save the current scroll position!
   5873                 ss.selStart = start;
   5874                 ss.selEnd = end;
   5875             }
   5876 
   5877             if (isFocused() && start >= 0 && end >= 0) {
   5878                 ss.frozenWithFocus = true;
   5879             }
   5880 
   5881             ss.error = getError();
   5882 
   5883             if (mEditor != null) {
   5884                 ss.editorState = mEditor.saveInstanceState();
   5885             }
   5886             return ss;
   5887         }
   5888 
   5889         return superState;
   5890     }
   5891 
   5892     void removeMisspelledSpans(Spannable spannable) {
   5893         SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
   5894                 SuggestionSpan.class);
   5895         for (int i = 0; i < suggestionSpans.length; i++) {
   5896             int flags = suggestionSpans[i].getFlags();
   5897             if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
   5898                     && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
   5899                 spannable.removeSpan(suggestionSpans[i]);
   5900             }
   5901         }
   5902     }
   5903 
   5904     @Override
   5905     public void onRestoreInstanceState(Parcelable state) {
   5906         if (!(state instanceof SavedState)) {
   5907             super.onRestoreInstanceState(state);
   5908             return;
   5909         }
   5910 
   5911         SavedState ss = (SavedState) state;
   5912         super.onRestoreInstanceState(ss.getSuperState());
   5913 
   5914         // XXX restore buffer type too, as well as lots of other stuff
   5915         if (ss.text != null) {
   5916             setText(ss.text);
   5917         }
   5918 
   5919         if (ss.selStart >= 0 && ss.selEnd >= 0) {
   5920             if (mSpannable != null) {
   5921                 int len = mText.length();
   5922 
   5923                 if (ss.selStart > len || ss.selEnd > len) {
   5924                     String restored = "";
   5925 
   5926                     if (ss.text != null) {
   5927                         restored = "(restored) ";
   5928                     }
   5929 
   5930                     Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
   5931                             + " out of range for " + restored + "text " + mText);
   5932                 } else {
   5933                     Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
   5934 
   5935                     if (ss.frozenWithFocus) {
   5936                         createEditorIfNeeded();
   5937                         mEditor.mFrozenWithFocus = true;
   5938                     }
   5939                 }
   5940             }
   5941         }
   5942 
   5943         if (ss.error != null) {
   5944             final CharSequence error = ss.error;
   5945             // Display the error later, after the first layout pass
   5946             post(new Runnable() {
   5947                 public void run() {
   5948                     if (mEditor == null || !mEditor.mErrorWasChanged) {
   5949                         setError(error);
   5950                     }
   5951                 }
   5952             });
   5953         }
   5954 
   5955         if (ss.editorState != null) {
   5956             createEditorIfNeeded();
   5957             mEditor.restoreInstanceState(ss.editorState);
   5958         }
   5959     }
   5960 
   5961     /**
   5962      * Control whether this text view saves its entire text contents when
   5963      * freezing to an icicle, in addition to dynamic state such as cursor
   5964      * position.  By default this is false, not saving the text.  Set to true
   5965      * if the text in the text view is not being saved somewhere else in
   5966      * persistent storage (such as in a content provider) so that if the
   5967      * view is later thawed the user will not lose their data. For
   5968      * {@link android.widget.EditText} it is always enabled, regardless of
   5969      * the value of the attribute.
   5970      *
   5971      * @param freezesText Controls whether a frozen icicle should include the
   5972      * entire text data: true to include it, false to not.
   5973      *
   5974      * @attr ref android.R.styleable#TextView_freezesText
   5975      */
   5976     @android.view.RemotableViewMethod
   5977     public void setFreezesText(boolean freezesText) {
   5978         mFreezesText = freezesText;
   5979     }
   5980 
   5981     /**
   5982      * Return whether this text view is including its entire text contents
   5983      * in frozen icicles. For {@link android.widget.EditText} it always returns true.
   5984      *
   5985      * @return Returns true if text is included, false if it isn't.
   5986      *
   5987      * @see #setFreezesText
   5988      */
   5989     @InspectableProperty
   5990     public boolean getFreezesText() {
   5991         return mFreezesText;
   5992     }
   5993 
   5994     ///////////////////////////////////////////////////////////////////////////
   5995 
   5996     /**
   5997      * Sets the Factory used to create new {@link Editable Editables}.
   5998      *
   5999      * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
   6000      *
   6001      * @see android.text.Editable.Factory
   6002      * @see android.widget.TextView.BufferType#EDITABLE
   6003      */
   6004     public final void setEditableFactory(Editable.Factory factory) {
   6005         mEditableFactory = factory;
   6006         setText(mText);
   6007     }
   6008 
   6009     /**
   6010      * Sets the Factory used to create new {@link Spannable Spannables}.
   6011      *
   6012      * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
   6013      *
   6014      * @see android.text.Spannable.Factory
   6015      * @see android.widget.TextView.BufferType#SPANNABLE
   6016      */
   6017     public final void setSpannableFactory(Spannable.Factory factory) {
   6018         mSpannableFactory = factory;
   6019         setText(mText);
   6020     }
   6021 
   6022     /**
   6023      * Sets the text to be displayed. TextView <em>does not</em> accept
   6024      * HTML-like formatting, which you can do with text strings in XML resource files.
   6025      * To style your strings, attach android.text.style.* objects to a
   6026      * {@link android.text.SpannableString}, or see the
   6027      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
   6028      * Available Resource Types</a> documentation for an example of setting
   6029      * formatted text in the XML resource file.
   6030      * <p/>
   6031      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
   6032      * intermediate {@link Spannable Spannables}. Likewise it will use
   6033      * {@link android.text.Editable.Factory} to create final or intermediate
   6034      * {@link Editable Editables}.
   6035      *
   6036      * If the passed text is a {@link PrecomputedText} but the parameters used to create the
   6037      * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure
   6038      * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this.
   6039      *
   6040      * @param text text to be displayed
   6041      *
   6042      * @attr ref android.R.styleable#TextView_text
   6043      * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the
   6044      *                                  parameters used to create the PrecomputedText mismatches
   6045      *                                  with this TextView.
   6046      */
   6047     @android.view.RemotableViewMethod
   6048     public final void setText(CharSequence text) {
   6049         setText(text, mBufferType);
   6050     }
   6051 
   6052     /**
   6053      * Sets the text to be displayed but retains the cursor position. Same as
   6054      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
   6055      * new text.
   6056      * <p/>
   6057      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
   6058      * intermediate {@link Spannable Spannables}. Likewise it will use
   6059      * {@link android.text.Editable.Factory} to create final or intermediate
   6060      * {@link Editable Editables}.
   6061      *
   6062      * @param text text to be displayed
   6063      *
   6064      * @see #setText(CharSequence)
   6065      */
   6066     @android.view.RemotableViewMethod
   6067     public final void setTextKeepState(CharSequence text) {
   6068         setTextKeepState(text, mBufferType);
   6069     }
   6070 
   6071     /**
   6072      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
   6073      * <p/>
   6074      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
   6075      * intermediate {@link Spannable Spannables}. Likewise it will use
   6076      * {@link android.text.Editable.Factory} to create final or intermediate
   6077      * {@link Editable Editables}.
   6078      *
   6079      * Subclasses overriding this method should ensure that the following post condition holds,
   6080      * in order to guarantee the safety of the view's measurement and layout operations:
   6081      * regardless of the input, after calling #setText both {@code mText} and {@code mTransformed}
   6082      * will be different from {@code null}.
   6083      *
   6084      * @param text text to be displayed
   6085      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
   6086      *              stored as a static text, styleable/spannable text, or editable text
   6087      *
   6088      * @see #setText(CharSequence)
   6089      * @see android.widget.TextView.BufferType
   6090      * @see #setSpannableFactory(Spannable.Factory)
   6091      * @see #setEditableFactory(Editable.Factory)
   6092      *
   6093      * @attr ref android.R.styleable#TextView_text
   6094      * @attr ref android.R.styleable#TextView_bufferType
   6095      */
   6096     public void setText(CharSequence text, BufferType type) {
   6097         setText(text, type, true, 0);
   6098 
   6099         if (mCharWrapper != null) {
   6100             mCharWrapper.mChars = null;
   6101         }
   6102     }
   6103 
   6104     @UnsupportedAppUsage
   6105     private void setText(CharSequence text, BufferType type,
   6106                          boolean notifyBefore, int oldlen) {
   6107         mTextSetFromXmlOrResourceId = false;
   6108         if (text == null) {
   6109             text = "";
   6110         }
   6111 
   6112         // If suggestions are not enabled, remove the suggestion spans from the text
   6113         if (!isSuggestionsEnabled()) {
   6114             text = removeSuggestionSpans(text);
   6115         }
   6116 
   6117         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
   6118 
   6119         if (text instanceof Spanned
   6120                 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
   6121             if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
   6122                 setHorizontalFadingEdgeEnabled(true);
   6123                 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
   6124             } else {
   6125                 setHorizontalFadingEdgeEnabled(false);
   6126                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   6127             }
   6128             setEllipsize(TextUtils.TruncateAt.MARQUEE);
   6129         }
   6130 
   6131         int n = mFilters.length;
   6132         for (int i = 0; i < n; i++) {
   6133             CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
   6134             if (out != null) {
   6135                 text = out;
   6136             }
   6137         }
   6138 
   6139         if (notifyBefore) {
   6140             if (mText != null) {
   6141                 oldlen = mText.length();
   6142                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
   6143             } else {
   6144                 sendBeforeTextChanged("", 0, 0, text.length());
   6145             }
   6146         }
   6147 
   6148         boolean needEditableForNotification = false;
   6149 
   6150         if (mListeners != null && mListeners.size() != 0) {
   6151             needEditableForNotification = true;
   6152         }
   6153 
   6154         PrecomputedText precomputed =
   6155                 (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
   6156         if (type == BufferType.EDITABLE || getKeyListener() != null
   6157                 || needEditableForNotification) {
   6158             createEditorIfNeeded();
   6159             mEditor.forgetUndoRedo();
   6160             Editable t = mEditableFactory.newEditable(text);
   6161             text = t;
   6162             setFilters(t, mFilters);
   6163             InputMethodManager imm = getInputMethodManager();
   6164             if (imm != null) imm.restartInput(this);
   6165         } else if (precomputed != null) {
   6166             if (mTextDir == null) {
   6167                 mTextDir = getTextDirectionHeuristic();
   6168             }
   6169             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
   6170                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
   6171                             mHyphenationFrequency);
   6172             switch (checkResult) {
   6173                 case PrecomputedText.Params.UNUSABLE:
   6174                     throw new IllegalArgumentException(
   6175                         "PrecomputedText's Parameters don't match the parameters of this TextView."
   6176                         + "Consider using setTextMetricsParams(precomputedText.getParams()) "
   6177                         + "to override the settings of this TextView: "
   6178                         + "PrecomputedText: " + precomputed.getParams()
   6179                         + "TextView: " + getTextMetricsParams());
   6180                 case PrecomputedText.Params.NEED_RECOMPUTE:
   6181                     precomputed = PrecomputedText.create(precomputed, getTextMetricsParams());
   6182                     break;
   6183                 case PrecomputedText.Params.USABLE:
   6184                     // pass through
   6185             }
   6186         } else if (type == BufferType.SPANNABLE || mMovement != null) {
   6187             text = mSpannableFactory.newSpannable(text);
   6188         } else if (!(text instanceof CharWrapper)) {
   6189             text = TextUtils.stringOrSpannedString(text);
   6190         }
   6191 
   6192         if (mAutoLinkMask != 0) {
   6193             Spannable s2;
   6194 
   6195             if (type == BufferType.EDITABLE || text instanceof Spannable) {
   6196                 s2 = (Spannable) text;
   6197             } else {
   6198                 s2 = mSpannableFactory.newSpannable(text);
   6199             }
   6200 
   6201             if (Linkify.addLinks(s2, mAutoLinkMask)) {
   6202                 text = s2;
   6203                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
   6204 
   6205                 /*
   6206                  * We must go ahead and set the text before changing the
   6207                  * movement method, because setMovementMethod() may call
   6208                  * setText() again to try to upgrade the buffer type.
   6209                  */
   6210                 setTextInternal(text);
   6211 
   6212                 // Do not change the movement method for text that support text selection as it
   6213                 // would prevent an arbitrary cursor displacement.
   6214                 if (mLinksClickable && !textCanBeSelected()) {
   6215                     setMovementMethod(LinkMovementMethod.getInstance());
   6216                 }
   6217             }
   6218         }
   6219 
   6220         mBufferType = type;
   6221         setTextInternal(text);
   6222 
   6223         if (mTransformation == null) {
   6224             mTransformed = text;
   6225         } else {
   6226             mTransformed = mTransformation.getTransformation(text, this);
   6227         }
   6228         if (mTransformed == null) {
   6229             // Should not happen if the transformation method follows the non-null postcondition.
   6230             mTransformed = "";
   6231         }
   6232 
   6233         final int textLength = text.length();
   6234 
   6235         if (text instanceof Spannable && !mAllowTransformationLengthChange) {
   6236             Spannable sp = (Spannable) text;
   6237 
   6238             // Remove any ChangeWatchers that might have come from other TextViews.
   6239             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
   6240             final int count = watchers.length;
   6241             for (int i = 0; i < count; i++) {
   6242                 sp.removeSpan(watchers[i]);
   6243             }
   6244 
   6245             if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
   6246 
   6247             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
   6248                     | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
   6249 
   6250             if (mEditor != null) mEditor.addSpanWatchers(sp);
   6251 
   6252             if (mTransformation != null) {
   6253                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   6254             }
   6255 
   6256             if (mMovement != null) {
   6257                 mMovement.initialize(this, (Spannable) text);
   6258 
   6259                 /*
   6260                  * Initializing the movement method will have set the
   6261                  * selection, so reset mSelectionMoved to keep that from
   6262                  * interfering with the normal on-focus selection-setting.
   6263                  */
   6264                 if (mEditor != null) mEditor.mSelectionMoved = false;
   6265             }
   6266         }
   6267 
   6268         if (mLayout != null) {
   6269             checkForRelayout();
   6270         }
   6271 
   6272         sendOnTextChanged(text, 0, oldlen, textLength);
   6273         onTextChanged(text, 0, oldlen, textLength);
   6274 
   6275         notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
   6276 
   6277         if (needEditableForNotification) {
   6278             sendAfterTextChanged((Editable) text);
   6279         } else {
   6280             notifyListeningManagersAfterTextChanged();
   6281         }
   6282 
   6283         // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
   6284         if (mEditor != null) mEditor.prepareCursorControllers();
   6285     }
   6286 
   6287     /**
   6288      * Sets the TextView to display the specified slice of the specified
   6289      * char array. You must promise that you will not change the contents
   6290      * of the array except for right before another call to setText(),
   6291      * since the TextView has no way to know that the text
   6292      * has changed and that it needs to invalidate and re-layout.
   6293      *
   6294      * @param text char array to be displayed
   6295      * @param start start index in the char array
   6296      * @param len length of char count after {@code start}
   6297      */
   6298     public final void setText(char[] text, int start, int len) {
   6299         int oldlen = 0;
   6300 
   6301         if (start < 0 || len < 0 || start + len > text.length) {
   6302             throw new IndexOutOfBoundsException(start + ", " + len);
   6303         }
   6304 
   6305         /*
   6306          * We must do the before-notification here ourselves because if
   6307          * the old text is a CharWrapper we destroy it before calling
   6308          * into the normal path.
   6309          */
   6310         if (mText != null) {
   6311             oldlen = mText.length();
   6312             sendBeforeTextChanged(mText, 0, oldlen, len);
   6313         } else {
   6314             sendBeforeTextChanged("", 0, 0, len);
   6315         }
   6316 
   6317         if (mCharWrapper == null) {
   6318             mCharWrapper = new CharWrapper(text, start, len);
   6319         } else {
   6320             mCharWrapper.set(text, start, len);
   6321         }
   6322 
   6323         setText(mCharWrapper, mBufferType, false, oldlen);
   6324     }
   6325 
   6326     /**
   6327      * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
   6328      * the cursor position. Same as
   6329      * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
   6330      * position (if any) is retained in the new text.
   6331      * <p/>
   6332      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
   6333      * intermediate {@link Spannable Spannables}. Likewise it will use
   6334      * {@link android.text.Editable.Factory} to create final or intermediate
   6335      * {@link Editable Editables}.
   6336      *
   6337      * @param text text to be displayed
   6338      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
   6339      *              stored as a static text, styleable/spannable text, or editable text
   6340      *
   6341      * @see #setText(CharSequence, android.widget.TextView.BufferType)
   6342      */
   6343     public final void setTextKeepState(CharSequence text, BufferType type) {
   6344         int start = getSelectionStart();
   6345         int end = getSelectionEnd();
   6346         int len = text.length();
   6347 
   6348         setText(text, type);
   6349 
   6350         if (start >= 0 || end >= 0) {
   6351             if (mSpannable != null) {
   6352                 Selection.setSelection(mSpannable,
   6353                                        Math.max(0, Math.min(start, len)),
   6354                                        Math.max(0, Math.min(end, len)));
   6355             }
   6356         }
   6357     }
   6358 
   6359     /**
   6360      * Sets the text to be displayed using a string resource identifier.
   6361      *
   6362      * @param resid the resource identifier of the string resource to be displayed
   6363      *
   6364      * @see #setText(CharSequence)
   6365      *
   6366      * @attr ref android.R.styleable#TextView_text
   6367      */
   6368     @android.view.RemotableViewMethod
   6369     public final void setText(@StringRes int resid) {
   6370         setText(getContext().getResources().getText(resid));
   6371         mTextSetFromXmlOrResourceId = true;
   6372         mTextId = resid;
   6373     }
   6374 
   6375     /**
   6376      * Sets the text to be displayed using a string resource identifier and the
   6377      * {@link android.widget.TextView.BufferType}.
   6378      * <p/>
   6379      * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
   6380      * intermediate {@link Spannable Spannables}. Likewise it will use
   6381      * {@link android.text.Editable.Factory} to create final or intermediate
   6382      * {@link Editable Editables}.
   6383      *
   6384      * @param resid the resource identifier of the string resource to be displayed
   6385      * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
   6386      *              stored as a static text, styleable/spannable text, or editable text
   6387      *
   6388      * @see #setText(int)
   6389      * @see #setText(CharSequence)
   6390      * @see android.widget.TextView.BufferType
   6391      * @see #setSpannableFactory(Spannable.Factory)
   6392      * @see #setEditableFactory(Editable.Factory)
   6393      *
   6394      * @attr ref android.R.styleable#TextView_text
   6395      * @attr ref android.R.styleable#TextView_bufferType
   6396      */
   6397     public final void setText(@StringRes int resid, BufferType type) {
   6398         setText(getContext().getResources().getText(resid), type);
   6399         mTextSetFromXmlOrResourceId = true;
   6400         mTextId = resid;
   6401     }
   6402 
   6403     /**
   6404      * Sets the text to be displayed when the text of the TextView is empty.
   6405      * Null means to use the normal empty text. The hint does not currently
   6406      * participate in determining the size of the view.
   6407      *
   6408      * @attr ref android.R.styleable#TextView_hint
   6409      */
   6410     @android.view.RemotableViewMethod
   6411     public final void setHint(CharSequence hint) {
   6412         setHintInternal(hint);
   6413 
   6414         if (mEditor != null && isInputMethodTarget()) {
   6415             mEditor.reportExtractedText();
   6416         }
   6417     }
   6418 
   6419     private void setHintInternal(CharSequence hint) {
   6420         mHint = TextUtils.stringOrSpannedString(hint);
   6421 
   6422         if (mLayout != null) {
   6423             checkForRelayout();
   6424         }
   6425 
   6426         if (mText.length() == 0) {
   6427             invalidate();
   6428         }
   6429 
   6430         // Invalidate display list if hint is currently used
   6431         if (mEditor != null && mText.length() == 0 && mHint != null) {
   6432             mEditor.invalidateTextDisplayList();
   6433         }
   6434     }
   6435 
   6436     /**
   6437      * Sets the text to be displayed when the text of the TextView is empty,
   6438      * from a resource.
   6439      *
   6440      * @attr ref android.R.styleable#TextView_hint
   6441      */
   6442     @android.view.RemotableViewMethod
   6443     public final void setHint(@StringRes int resid) {
   6444         setHint(getContext().getResources().getText(resid));
   6445     }
   6446 
   6447     /**
   6448      * Returns the hint that is displayed when the text of the TextView
   6449      * is empty.
   6450      *
   6451      * @attr ref android.R.styleable#TextView_hint
   6452      */
   6453     @InspectableProperty
   6454     @ViewDebug.CapturedViewProperty
   6455     public CharSequence getHint() {
   6456         return mHint;
   6457     }
   6458 
   6459     /**
   6460      * Returns if the text is constrained to a single horizontally scrolling line ignoring new
   6461      * line characters instead of letting it wrap onto multiple lines.
   6462      *
   6463      * @attr ref android.R.styleable#TextView_singleLine
   6464      */
   6465     @InspectableProperty
   6466     public boolean isSingleLine() {
   6467         return mSingleLine;
   6468     }
   6469 
   6470     private static boolean isMultilineInputType(int type) {
   6471         return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
   6472                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
   6473     }
   6474 
   6475     /**
   6476      * Removes the suggestion spans.
   6477      */
   6478     CharSequence removeSuggestionSpans(CharSequence text) {
   6479         if (text instanceof Spanned) {
   6480             Spannable spannable;
   6481             if (text instanceof Spannable) {
   6482                 spannable = (Spannable) text;
   6483             } else {
   6484                 spannable = mSpannableFactory.newSpannable(text);
   6485             }
   6486 
   6487             SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
   6488             if (spans.length == 0) {
   6489                 return text;
   6490             } else {
   6491                 text = spannable;
   6492             }
   6493 
   6494             for (int i = 0; i < spans.length; i++) {
   6495                 spannable.removeSpan(spans[i]);
   6496             }
   6497         }
   6498         return text;
   6499     }
   6500 
   6501     /**
   6502      * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
   6503      * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
   6504      * to match the given content type.  If the given content type is {@link EditorInfo#TYPE_NULL}
   6505      * then a soft keyboard will not be displayed for this text view.
   6506      *
   6507      * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
   6508      * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
   6509      * type.
   6510      *
   6511      * @see #getInputType()
   6512      * @see #setRawInputType(int)
   6513      * @see android.text.InputType
   6514      * @attr ref android.R.styleable#TextView_inputType
   6515      */
   6516     public void setInputType(int type) {
   6517         final boolean wasPassword = isPasswordInputType(getInputType());
   6518         final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
   6519         setInputType(type, false);
   6520         final boolean isPassword = isPasswordInputType(type);
   6521         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
   6522         boolean forceUpdate = false;
   6523         if (isPassword) {
   6524             setTransformationMethod(PasswordTransformationMethod.getInstance());
   6525             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
   6526                     Typeface.NORMAL, -1 /* weight, not specifeid */);
   6527         } else if (isVisiblePassword) {
   6528             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   6529                 forceUpdate = true;
   6530             }
   6531             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE,
   6532                     Typeface.NORMAL, -1 /* weight, not specified */);
   6533         } else if (wasPassword || wasVisiblePassword) {
   6534             // not in password mode, clean up typeface and transformation
   6535             setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */,
   6536                     DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL,
   6537                     -1 /* weight, not specified */);
   6538             if (mTransformation == PasswordTransformationMethod.getInstance()) {
   6539                 forceUpdate = true;
   6540             }
   6541         }
   6542 
   6543         boolean singleLine = !isMultilineInputType(type);
   6544 
   6545         // We need to update the single line mode if it has changed or we
   6546         // were previously in password mode.
   6547         if (mSingleLine != singleLine || forceUpdate) {
   6548             // Change single line mode, but only change the transformation if
   6549             // we are not in password mode.
   6550             applySingleLine(singleLine, !isPassword, true);
   6551         }
   6552 
   6553         if (!isSuggestionsEnabled()) {
   6554             setTextInternal(removeSuggestionSpans(mText));
   6555         }
   6556 
   6557         InputMethodManager imm = getInputMethodManager();
   6558         if (imm != null) imm.restartInput(this);
   6559     }
   6560 
   6561     /**
   6562      * It would be better to rely on the input type for everything. A password inputType should have
   6563      * a password transformation. We should hence use isPasswordInputType instead of this method.
   6564      *
   6565      * We should:
   6566      * - Call setInputType in setKeyListener instead of changing the input type directly (which
   6567      * would install the correct transformation).
   6568      * - Refuse the installation of a non-password transformation in setTransformation if the input
   6569      * type is password.
   6570      *
   6571      * However, this is like this for legacy reasons and we cannot break existing apps. This method
   6572      * is useful since it matches what the user can see (obfuscated text or not).
   6573      *
   6574      * @return true if the current transformation method is of the password type.
   6575      */
   6576     boolean hasPasswordTransformationMethod() {
   6577         return mTransformation instanceof PasswordTransformationMethod;
   6578     }
   6579 
   6580     static boolean isPasswordInputType(int inputType) {
   6581         final int variation =
   6582                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
   6583         return variation
   6584                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
   6585                 || variation
   6586                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
   6587                 || variation
   6588                 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
   6589     }
   6590 
   6591     private static boolean isVisiblePasswordInputType(int inputType) {
   6592         final int variation =
   6593                 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
   6594         return variation
   6595                 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
   6596     }
   6597 
   6598     /**
   6599      * Directly change the content type integer of the text view, without
   6600      * modifying any other state.
   6601      * @see #setInputType(int)
   6602      * @see android.text.InputType
   6603      * @attr ref android.R.styleable#TextView_inputType
   6604      */
   6605     public void setRawInputType(int type) {
   6606         if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
   6607         createEditorIfNeeded();
   6608         mEditor.mInputType = type;
   6609     }
   6610 
   6611     /**
   6612      * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
   6613      *         a {@code Locale} object that can be used to customize key various listeners.
   6614      * @see DateKeyListener#getInstance(Locale)
   6615      * @see DateTimeKeyListener#getInstance(Locale)
   6616      * @see DigitsKeyListener#getInstance(Locale)
   6617      * @see TimeKeyListener#getInstance(Locale)
   6618      */
   6619     @Nullable
   6620     private Locale getCustomLocaleForKeyListenerOrNull() {
   6621         if (!mUseInternationalizedInput) {
   6622             // If the application does not target O, stick to the previous behavior.
   6623             return null;
   6624         }
   6625         final LocaleList locales = getImeHintLocales();
   6626         if (locales == null) {
   6627             // If the application does not explicitly specify IME hint locale, also stick to the
   6628             // previous behavior.
   6629             return null;
   6630         }
   6631         return locales.get(0);
   6632     }
   6633 
   6634     @UnsupportedAppUsage
   6635     private void setInputType(int type, boolean direct) {
   6636         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
   6637         KeyListener input;
   6638         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
   6639             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
   6640             TextKeyListener.Capitalize cap;
   6641             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
   6642                 cap = TextKeyListener.Capitalize.CHARACTERS;
   6643             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
   6644                 cap = TextKeyListener.Capitalize.WORDS;
   6645             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
   6646                 cap = TextKeyListener.Capitalize.SENTENCES;
   6647             } else {
   6648                 cap = TextKeyListener.Capitalize.NONE;
   6649             }
   6650             input = TextKeyListener.getInstance(autotext, cap);
   6651         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
   6652             final Locale locale = getCustomLocaleForKeyListenerOrNull();
   6653             input = DigitsKeyListener.getInstance(
   6654                     locale,
   6655                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
   6656                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
   6657             if (locale != null) {
   6658                 // Override type, if necessary for i18n.
   6659                 int newType = input.getInputType();
   6660                 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
   6661                 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
   6662                     // The class is different from the original class. So we need to override
   6663                     // 'type'. But we want to keep the password flag if it's there.
   6664                     if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
   6665                         newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
   6666                     }
   6667                     type = newType;
   6668                 }
   6669             }
   6670         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
   6671             final Locale locale = getCustomLocaleForKeyListenerOrNull();
   6672             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
   6673                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
   6674                     input = DateKeyListener.getInstance(locale);
   6675                     break;
   6676                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
   6677                     input = TimeKeyListener.getInstance(locale);
   6678                     break;
   6679                 default:
   6680                     input = DateTimeKeyListener.getInstance(locale);
   6681                     break;
   6682             }
   6683             if (mUseInternationalizedInput) {
   6684                 type = input.getInputType(); // Override type, if necessary for i18n.
   6685             }
   6686         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
   6687             input = DialerKeyListener.getInstance();
   6688         } else {
   6689             input = TextKeyListener.getInstance();
   6690         }
   6691         setRawInputType(type);
   6692         mListenerChanged = false;
   6693         if (direct) {
   6694             createEditorIfNeeded();
   6695             mEditor.mKeyListener = input;
   6696         } else {
   6697             setKeyListenerOnly(input);
   6698         }
   6699     }
   6700 
   6701     /**
   6702      * Get the type of the editable content.
   6703      *
   6704      * @see #setInputType(int)
   6705      * @see android.text.InputType
   6706      */
   6707     @InspectableProperty(flagMapping = {
   6708             @FlagEntry(name = "none", mask = 0xffffffff, target = InputType.TYPE_NULL),
   6709             @FlagEntry(
   6710                     name = "text",
   6711                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6712                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL),
   6713             @FlagEntry(
   6714                     name = "textUri",
   6715                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6716                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI),
   6717             @FlagEntry(
   6718                     name = "textEmailAddress",
   6719                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6720                     target = InputType.TYPE_CLASS_TEXT
   6721                             | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS),
   6722             @FlagEntry(
   6723                     name = "textEmailSubject",
   6724                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6725                     target = InputType.TYPE_CLASS_TEXT
   6726                             | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT),
   6727             @FlagEntry(
   6728                     name = "textShortMessage",
   6729                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6730                     target = InputType.TYPE_CLASS_TEXT
   6731                             | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE),
   6732             @FlagEntry(
   6733                     name = "textLongMessage",
   6734                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6735                     target = InputType.TYPE_CLASS_TEXT
   6736                             | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE),
   6737             @FlagEntry(
   6738                     name = "textPersonName",
   6739                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6740                     target = InputType.TYPE_CLASS_TEXT
   6741                             | InputType.TYPE_TEXT_VARIATION_PERSON_NAME),
   6742             @FlagEntry(
   6743                     name = "textPostalAddress",
   6744                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6745                     target = InputType.TYPE_CLASS_TEXT
   6746                             | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS),
   6747             @FlagEntry(
   6748                     name = "textPassword",
   6749                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6750                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD),
   6751             @FlagEntry(
   6752                     name = "textVisiblePassword",
   6753                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6754                     target = InputType.TYPE_CLASS_TEXT
   6755                             | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD),
   6756             @FlagEntry(
   6757                     name = "textWebEditText",
   6758                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6759                     target = InputType.TYPE_CLASS_TEXT
   6760                             | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT),
   6761             @FlagEntry(
   6762                     name = "textFilter",
   6763                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6764                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER),
   6765             @FlagEntry(
   6766                     name = "textPhonetic",
   6767                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6768                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC),
   6769             @FlagEntry(
   6770                     name = "textWebEmailAddress",
   6771                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6772                     target = InputType.TYPE_CLASS_TEXT
   6773                             | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS),
   6774             @FlagEntry(
   6775                     name = "textWebPassword",
   6776                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6777                     target = InputType.TYPE_CLASS_TEXT
   6778                             | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD),
   6779             @FlagEntry(
   6780                     name = "number",
   6781                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6782                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL),
   6783             @FlagEntry(
   6784                     name = "numberPassword",
   6785                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6786                     target = InputType.TYPE_CLASS_NUMBER
   6787                             | InputType.TYPE_NUMBER_VARIATION_PASSWORD),
   6788             @FlagEntry(
   6789                     name = "phone",
   6790                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6791                     target = InputType.TYPE_CLASS_PHONE),
   6792             @FlagEntry(
   6793                     name = "datetime",
   6794                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6795                     target = InputType.TYPE_CLASS_DATETIME
   6796                             | InputType.TYPE_DATETIME_VARIATION_NORMAL),
   6797             @FlagEntry(
   6798                     name = "date",
   6799                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6800                     target = InputType.TYPE_CLASS_DATETIME
   6801                             | InputType.TYPE_DATETIME_VARIATION_DATE),
   6802             @FlagEntry(
   6803                     name = "time",
   6804                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION,
   6805                     target = InputType.TYPE_CLASS_DATETIME
   6806                             | InputType.TYPE_DATETIME_VARIATION_TIME),
   6807             @FlagEntry(
   6808                     name = "textCapCharacters",
   6809                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6810                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS),
   6811             @FlagEntry(
   6812                     name = "textCapWords",
   6813                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6814                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS),
   6815             @FlagEntry(
   6816                     name = "textCapSentences",
   6817                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6818                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
   6819             @FlagEntry(
   6820                     name = "textAutoCorrect",
   6821                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6822                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT),
   6823             @FlagEntry(
   6824                     name = "textAutoComplete",
   6825                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6826                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE),
   6827             @FlagEntry(
   6828                     name = "textMultiLine",
   6829                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6830                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE),
   6831             @FlagEntry(
   6832                     name = "textImeMultiLine",
   6833                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6834                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE),
   6835             @FlagEntry(
   6836                     name = "textNoSuggestions",
   6837                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6838                     target = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS),
   6839             @FlagEntry(
   6840                     name = "numberSigned",
   6841                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6842                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED),
   6843             @FlagEntry(
   6844                     name = "numberDecimal",
   6845                     mask = InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_FLAGS,
   6846                     target = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL),
   6847     })
   6848     public int getInputType() {
   6849         return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
   6850     }
   6851 
   6852     /**
   6853      * Change the editor type integer associated with the text view, which
   6854      * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
   6855      * when it has focus.
   6856      * @see #getImeOptions
   6857      * @see android.view.inputmethod.EditorInfo
   6858      * @attr ref android.R.styleable#TextView_imeOptions
   6859      */
   6860     public void setImeOptions(int imeOptions) {
   6861         createEditorIfNeeded();
   6862         mEditor.createInputContentTypeIfNeeded();
   6863         mEditor.mInputContentType.imeOptions = imeOptions;
   6864     }
   6865 
   6866     /**
   6867      * Get the type of the Input Method Editor (IME).
   6868      * @return the type of the IME
   6869      * @see #setImeOptions(int)
   6870      * @see EditorInfo
   6871      */
   6872     @InspectableProperty(flagMapping = {
   6873             @FlagEntry(name = "normal", mask = 0xffffffff, target = EditorInfo.IME_NULL),
   6874             @FlagEntry(
   6875                     name = "actionUnspecified",
   6876                     mask = EditorInfo.IME_MASK_ACTION,
   6877                     target = EditorInfo.IME_ACTION_UNSPECIFIED),
   6878             @FlagEntry(
   6879                     name = "actionNone",
   6880                     mask = EditorInfo.IME_MASK_ACTION,
   6881                     target = EditorInfo.IME_ACTION_NONE),
   6882             @FlagEntry(
   6883                     name = "actionGo",
   6884                     mask = EditorInfo.IME_MASK_ACTION,
   6885                     target = EditorInfo.IME_ACTION_GO),
   6886             @FlagEntry(
   6887                     name = "actionSearch",
   6888                     mask = EditorInfo.IME_MASK_ACTION,
   6889                     target = EditorInfo.IME_ACTION_SEARCH),
   6890             @FlagEntry(
   6891                     name = "actionSend",
   6892                     mask = EditorInfo.IME_MASK_ACTION,
   6893                     target = EditorInfo.IME_ACTION_SEND),
   6894             @FlagEntry(
   6895                     name = "actionNext",
   6896                     mask = EditorInfo.IME_MASK_ACTION,
   6897                     target = EditorInfo.IME_ACTION_NEXT),
   6898             @FlagEntry(
   6899                     name = "actionDone",
   6900                     mask = EditorInfo.IME_MASK_ACTION,
   6901                     target = EditorInfo.IME_ACTION_DONE),
   6902             @FlagEntry(
   6903                     name = "actionPrevious",
   6904                     mask = EditorInfo.IME_MASK_ACTION,
   6905                     target = EditorInfo.IME_ACTION_PREVIOUS),
   6906             @FlagEntry(name = "flagForceAscii", target = EditorInfo.IME_FLAG_FORCE_ASCII),
   6907             @FlagEntry(name = "flagNavigateNext", target = EditorInfo.IME_FLAG_NAVIGATE_NEXT),
   6908             @FlagEntry(
   6909                     name = "flagNavigatePrevious",
   6910                     target = EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS),
   6911             @FlagEntry(
   6912                     name = "flagNoAccessoryAction",
   6913                     target = EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION),
   6914             @FlagEntry(name = "flagNoEnterAction", target = EditorInfo.IME_FLAG_NO_ENTER_ACTION),
   6915             @FlagEntry(name = "flagNoExtractUi", target = EditorInfo.IME_FLAG_NO_EXTRACT_UI),
   6916             @FlagEntry(name = "flagNoFullscreen", target = EditorInfo.IME_FLAG_NO_FULLSCREEN),
   6917             @FlagEntry(
   6918                     name = "flagNoPersonalizedLearning",
   6919                     target = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING),
   6920     })
   6921     public int getImeOptions() {
   6922         return mEditor != null && mEditor.mInputContentType != null
   6923                 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
   6924     }
   6925 
   6926     /**
   6927      * Change the custom IME action associated with the text view, which
   6928      * will be reported to an IME with {@link EditorInfo#actionLabel}
   6929      * and {@link EditorInfo#actionId} when it has focus.
   6930      * @see #getImeActionLabel
   6931      * @see #getImeActionId
   6932      * @see android.view.inputmethod.EditorInfo
   6933      * @attr ref android.R.styleable#TextView_imeActionLabel
   6934      * @attr ref android.R.styleable#TextView_imeActionId
   6935      */
   6936     public void setImeActionLabel(CharSequence label, int actionId) {
   6937         createEditorIfNeeded();
   6938         mEditor.createInputContentTypeIfNeeded();
   6939         mEditor.mInputContentType.imeActionLabel = label;
   6940         mEditor.mInputContentType.imeActionId = actionId;
   6941     }
   6942 
   6943     /**
   6944      * Get the IME action label previous set with {@link #setImeActionLabel}.
   6945      *
   6946      * @see #setImeActionLabel
   6947      * @see android.view.inputmethod.EditorInfo
   6948      */
   6949     @InspectableProperty
   6950     public CharSequence getImeActionLabel() {
   6951         return mEditor != null && mEditor.mInputContentType != null
   6952                 ? mEditor.mInputContentType.imeActionLabel : null;
   6953     }
   6954 
   6955     /**
   6956      * Get the IME action ID previous set with {@link #setImeActionLabel}.
   6957      *
   6958      * @see #setImeActionLabel
   6959      * @see android.view.inputmethod.EditorInfo
   6960      */
   6961     @InspectableProperty
   6962     public int getImeActionId() {
   6963         return mEditor != null && mEditor.mInputContentType != null
   6964                 ? mEditor.mInputContentType.imeActionId : 0;
   6965     }
   6966 
   6967     /**
   6968      * Set a special listener to be called when an action is performed
   6969      * on the text view.  This will be called when the enter key is pressed,
   6970      * or when an action supplied to the IME is selected by the user.  Setting
   6971      * this means that the normal hard key event will not insert a newline
   6972      * into the text view, even if it is multi-line; holding down the ALT
   6973      * modifier will, however, allow the user to insert a newline character.
   6974      */
   6975     public void setOnEditorActionListener(OnEditorActionListener l) {
   6976         createEditorIfNeeded();
   6977         mEditor.createInputContentTypeIfNeeded();
   6978         mEditor.mInputContentType.onEditorActionListener = l;
   6979     }
   6980 
   6981     /**
   6982      * Called when an attached input method calls
   6983      * {@link InputConnection#performEditorAction(int)
   6984      * InputConnection.performEditorAction()}
   6985      * for this text view.  The default implementation will call your action
   6986      * listener supplied to {@link #setOnEditorActionListener}, or perform
   6987      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
   6988      * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
   6989      * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
   6990      * EditorInfo.IME_ACTION_DONE}.
   6991      *
   6992      * <p>For backwards compatibility, if no IME options have been set and the
   6993      * text view would not normally advance focus on enter, then
   6994      * the NEXT and DONE actions received here will be turned into an enter
   6995      * key down/up pair to go through the normal key handling.
   6996      *
   6997      * @param actionCode The code of the action being performed.
   6998      *
   6999      * @see #setOnEditorActionListener
   7000      */
   7001     public void onEditorAction(int actionCode) {
   7002         final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
   7003         if (ict != null) {
   7004             if (ict.onEditorActionListener != null) {
   7005                 if (ict.onEditorActionListener.onEditorAction(this,
   7006                         actionCode, null)) {
   7007                     return;
   7008                 }
   7009             }
   7010 
   7011             // This is the handling for some default action.
   7012             // Note that for backwards compatibility we don't do this
   7013             // default handling if explicit ime options have not been given,
   7014             // instead turning this into the normal enter key codes that an
   7015             // app may be expecting.
   7016             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
   7017                 View v = focusSearch(FOCUS_FORWARD);
   7018                 if (v != null) {
   7019                     if (!v.requestFocus(FOCUS_FORWARD)) {
   7020                         throw new IllegalStateException("focus search returned a view "
   7021                                 + "that wasn't able to take focus!");
   7022                     }
   7023                 }
   7024                 return;
   7025 
   7026             } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
   7027                 View v = focusSearch(FOCUS_BACKWARD);
   7028                 if (v != null) {
   7029                     if (!v.requestFocus(FOCUS_BACKWARD)) {
   7030                         throw new IllegalStateException("focus search returned a view "
   7031                                 + "that wasn't able to take focus!");
   7032                     }
   7033                 }
   7034                 return;
   7035 
   7036             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
   7037                 InputMethodManager imm = getInputMethodManager();
   7038                 if (imm != null && imm.isActive(this)) {
   7039                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   7040                 }
   7041                 return;
   7042             }
   7043         }
   7044 
   7045         ViewRootImpl viewRootImpl = getViewRootImpl();
   7046         if (viewRootImpl != null) {
   7047             long eventTime = SystemClock.uptimeMillis();
   7048             viewRootImpl.dispatchKeyFromIme(
   7049                     new KeyEvent(eventTime, eventTime,
   7050                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
   7051                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
   7052                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   7053                     | KeyEvent.FLAG_EDITOR_ACTION));
   7054             viewRootImpl.dispatchKeyFromIme(
   7055                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
   7056                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
   7057                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
   7058                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
   7059                     | KeyEvent.FLAG_EDITOR_ACTION));
   7060         }
   7061     }
   7062 
   7063     /**
   7064      * Set the private content type of the text, which is the
   7065      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
   7066      * field that will be filled in when creating an input connection.
   7067      *
   7068      * @see #getPrivateImeOptions()
   7069      * @see EditorInfo#privateImeOptions
   7070      * @attr ref android.R.styleable#TextView_privateImeOptions
   7071      */
   7072     public void setPrivateImeOptions(String type) {
   7073         createEditorIfNeeded();
   7074         mEditor.createInputContentTypeIfNeeded();
   7075         mEditor.mInputContentType.privateImeOptions = type;
   7076     }
   7077 
   7078     /**
   7079      * Get the private type of the content.
   7080      *
   7081      * @see #setPrivateImeOptions(String)
   7082      * @see EditorInfo#privateImeOptions
   7083      */
   7084     @InspectableProperty
   7085     public String getPrivateImeOptions() {
   7086         return mEditor != null && mEditor.mInputContentType != null
   7087                 ? mEditor.mInputContentType.privateImeOptions : null;
   7088     }
   7089 
   7090     /**
   7091      * Set the extra input data of the text, which is the
   7092      * {@link EditorInfo#extras TextBoxAttribute.extras}
   7093      * Bundle that will be filled in when creating an input connection.  The
   7094      * given integer is the resource identifier of an XML resource holding an
   7095      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
   7096      *
   7097      * @see #getInputExtras(boolean)
   7098      * @see EditorInfo#extras
   7099      * @attr ref android.R.styleable#TextView_editorExtras
   7100      */
   7101     public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
   7102         createEditorIfNeeded();
   7103         XmlResourceParser parser = getResources().getXml(xmlResId);
   7104         mEditor.createInputContentTypeIfNeeded();
   7105         mEditor.mInputContentType.extras = new Bundle();
   7106         getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
   7107     }
   7108 
   7109     /**
   7110      * Retrieve the input extras currently associated with the text view, which
   7111      * can be viewed as well as modified.
   7112      *
   7113      * @param create If true, the extras will be created if they don't already
   7114      * exist.  Otherwise, null will be returned if none have been created.
   7115      * @see #setInputExtras(int)
   7116      * @see EditorInfo#extras
   7117      * @attr ref android.R.styleable#TextView_editorExtras
   7118      */
   7119     public Bundle getInputExtras(boolean create) {
   7120         if (mEditor == null && !create) return null;
   7121         createEditorIfNeeded();
   7122         if (mEditor.mInputContentType == null) {
   7123             if (!create) return null;
   7124             mEditor.createInputContentTypeIfNeeded();
   7125         }
   7126         if (mEditor.mInputContentType.extras == null) {
   7127             if (!create) return null;
   7128             mEditor.mInputContentType.extras = new Bundle();
   7129         }
   7130         return mEditor.mInputContentType.extras;
   7131     }
   7132 
   7133     /**
   7134      * Change "hint" locales associated with the text view, which will be reported to an IME with
   7135      * {@link EditorInfo#hintLocales} when it has focus.
   7136      *
   7137      * Starting with Android O, this also causes internationalized listeners to be created (or
   7138      * change locale) based on the first locale in the input locale list.
   7139      *
   7140      * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
   7141      * call {@link InputMethodManager#restartInput(View)}.</p>
   7142      * @param hintLocales List of the languages that the user is supposed to switch to no matter
   7143      * what input method subtype is currently used. Set {@code null} to clear the current "hint".
   7144      * @see #getImeHintLocales()
   7145      * @see android.view.inputmethod.EditorInfo#hintLocales
   7146      */
   7147     public void setImeHintLocales(@Nullable LocaleList hintLocales) {
   7148         createEditorIfNeeded();
   7149         mEditor.createInputContentTypeIfNeeded();
   7150         mEditor.mInputContentType.imeHintLocales = hintLocales;
   7151         if (mUseInternationalizedInput) {
   7152             changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
   7153         }
   7154     }
   7155 
   7156     /**
   7157      * @return The current languages list "hint". {@code null} when no "hint" is available.
   7158      * @see #setImeHintLocales(LocaleList)
   7159      * @see android.view.inputmethod.EditorInfo#hintLocales
   7160      */
   7161     @Nullable
   7162     public LocaleList getImeHintLocales() {
   7163         if (mEditor == null) {
   7164             return null;
   7165         }
   7166         if (mEditor.mInputContentType == null) {
   7167             return null;
   7168         }
   7169         return mEditor.mInputContentType.imeHintLocales;
   7170     }
   7171 
   7172     /**
   7173      * Returns the error message that was set to be displayed with
   7174      * {@link #setError}, or <code>null</code> if no error was set
   7175      * or if it the error was cleared by the widget after user input.
   7176      */
   7177     public CharSequence getError() {
   7178         return mEditor == null ? null : mEditor.mError;
   7179     }
   7180 
   7181     /**
   7182      * Sets the right-hand compound drawable of the TextView to the "error"
   7183      * icon and sets an error message that will be displayed in a popup when
   7184      * the TextView has focus.  The icon and error message will be reset to
   7185      * null when any key events cause changes to the TextView's text.  If the
   7186      * <code>error</code> is <code>null</code>, the error message and icon
   7187      * will be cleared.
   7188      */
   7189     @android.view.RemotableViewMethod
   7190     public void setError(CharSequence error) {
   7191         if (error == null) {
   7192             setError(null, null);
   7193         } else {
   7194             Drawable dr = getContext().getDrawable(
   7195                     com.android.internal.R.drawable.indicator_input_error);
   7196 
   7197             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
   7198             setError(error, dr);
   7199         }
   7200     }
   7201 
   7202     /**
   7203      * Sets the right-hand compound drawable of the TextView to the specified
   7204      * icon and sets an error message that will be displayed in a popup when
   7205      * the TextView has focus.  The icon and error message will be reset to
   7206      * null when any key events cause changes to the TextView's text.  The
   7207      * drawable must already have had {@link Drawable#setBounds} set on it.
   7208      * If the <code>error</code> is <code>null</code>, the error message will
   7209      * be cleared (and you should provide a <code>null</code> icon as well).
   7210      */
   7211     public void setError(CharSequence error, Drawable icon) {
   7212         createEditorIfNeeded();
   7213         mEditor.setError(error, icon);
   7214         notifyViewAccessibilityStateChangedIfNeeded(
   7215                 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
   7216     }
   7217 
   7218     @Override
   7219     protected boolean setFrame(int l, int t, int r, int b) {
   7220         boolean result = super.setFrame(l, t, r, b);
   7221 
   7222         if (mEditor != null) mEditor.setFrame();
   7223 
   7224         restartMarqueeIfNeeded();
   7225 
   7226         return result;
   7227     }
   7228 
   7229     private void restartMarqueeIfNeeded() {
   7230         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   7231             mRestartMarquee = false;
   7232             startMarquee();
   7233         }
   7234     }
   7235 
   7236     /**
   7237      * Sets the list of input filters that will be used if the buffer is
   7238      * Editable. Has no effect otherwise.
   7239      *
   7240      * @attr ref android.R.styleable#TextView_maxLength
   7241      */
   7242     public void setFilters(InputFilter[] filters) {
   7243         if (filters == null) {
   7244             throw new IllegalArgumentException();
   7245         }
   7246 
   7247         mFilters = filters;
   7248 
   7249         if (mText instanceof Editable) {
   7250             setFilters((Editable) mText, filters);
   7251         }
   7252     }
   7253 
   7254     /**
   7255      * Sets the list of input filters on the specified Editable,
   7256      * and includes mInput in the list if it is an InputFilter.
   7257      */
   7258     private void setFilters(Editable e, InputFilter[] filters) {
   7259         if (mEditor != null) {
   7260             final boolean undoFilter = mEditor.mUndoInputFilter != null;
   7261             final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
   7262             int num = 0;
   7263             if (undoFilter) num++;
   7264             if (keyFilter) num++;
   7265             if (num > 0) {
   7266                 InputFilter[] nf = new InputFilter[filters.length + num];
   7267 
   7268                 System.arraycopy(filters, 0, nf, 0, filters.length);
   7269                 num = 0;
   7270                 if (undoFilter) {
   7271                     nf[filters.length] = mEditor.mUndoInputFilter;
   7272                     num++;
   7273                 }
   7274                 if (keyFilter) {
   7275                     nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
   7276                 }
   7277 
   7278                 e.setFilters(nf);
   7279                 return;
   7280             }
   7281         }
   7282         e.setFilters(filters);
   7283     }
   7284 
   7285     /**
   7286      * Returns the current list of input filters.
   7287      *
   7288      * @attr ref android.R.styleable#TextView_maxLength
   7289      */
   7290     public InputFilter[] getFilters() {
   7291         return mFilters;
   7292     }
   7293 
   7294     /////////////////////////////////////////////////////////////////////////
   7295 
   7296     private int getBoxHeight(Layout l) {
   7297         Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
   7298         int padding = (l == mHintLayout)
   7299                 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
   7300                 : getExtendedPaddingTop() + getExtendedPaddingBottom();
   7301         return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
   7302     }
   7303 
   7304     @UnsupportedAppUsage
   7305     int getVerticalOffset(boolean forceNormal) {
   7306         int voffset = 0;
   7307         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   7308 
   7309         Layout l = mLayout;
   7310         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   7311             l = mHintLayout;
   7312         }
   7313 
   7314         if (gravity != Gravity.TOP) {
   7315             int boxht = getBoxHeight(l);
   7316             int textht = l.getHeight();
   7317 
   7318             if (textht < boxht) {
   7319                 if (gravity == Gravity.BOTTOM) {
   7320                     voffset = boxht - textht;
   7321                 } else { // (gravity == Gravity.CENTER_VERTICAL)
   7322                     voffset = (boxht - textht) >> 1;
   7323                 }
   7324             }
   7325         }
   7326         return voffset;
   7327     }
   7328 
   7329     private int getBottomVerticalOffset(boolean forceNormal) {
   7330         int voffset = 0;
   7331         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   7332 
   7333         Layout l = mLayout;
   7334         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
   7335             l = mHintLayout;
   7336         }
   7337 
   7338         if (gravity != Gravity.BOTTOM) {
   7339             int boxht = getBoxHeight(l);
   7340             int textht = l.getHeight();
   7341 
   7342             if (textht < boxht) {
   7343                 if (gravity == Gravity.TOP) {
   7344                     voffset = boxht - textht;
   7345                 } else { // (gravity == Gravity.CENTER_VERTICAL)
   7346                     voffset = (boxht - textht) >> 1;
   7347                 }
   7348             }
   7349         }
   7350         return voffset;
   7351     }
   7352 
   7353     void invalidateCursorPath() {
   7354         if (mHighlightPathBogus) {
   7355             invalidateCursor();
   7356         } else {
   7357             final int horizontalPadding = getCompoundPaddingLeft();
   7358             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
   7359 
   7360             if (mEditor.mDrawableForCursor == null) {
   7361                 synchronized (TEMP_RECTF) {
   7362                     /*
   7363                      * The reason for this concern about the thickness of the
   7364                      * cursor and doing the floor/ceil on the coordinates is that
   7365                      * some EditTexts (notably textfields in the Browser) have
   7366                      * anti-aliased text where not all the characters are
   7367                      * necessarily at integer-multiple locations.  This should
   7368                      * make sure the entire cursor gets invalidated instead of
   7369                      * sometimes missing half a pixel.
   7370                      */
   7371                     float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
   7372                     if (thick < 1.0f) {
   7373                         thick = 1.0f;
   7374                     }
   7375 
   7376                     thick /= 2.0f;
   7377 
   7378                     // mHighlightPath is guaranteed to be non null at that point.
   7379                     mHighlightPath.computeBounds(TEMP_RECTF, false);
   7380 
   7381                     invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
   7382                             (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
   7383                             (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
   7384                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
   7385                 }
   7386             } else {
   7387                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
   7388                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
   7389                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
   7390             }
   7391         }
   7392     }
   7393 
   7394     void invalidateCursor() {
   7395         int where = getSelectionEnd();
   7396 
   7397         invalidateCursor(where, where, where);
   7398     }
   7399 
   7400     private void invalidateCursor(int a, int b, int c) {
   7401         if (a >= 0 || b >= 0 || c >= 0) {
   7402             int start = Math.min(Math.min(a, b), c);
   7403             int end = Math.max(Math.max(a, b), c);
   7404             invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
   7405         }
   7406     }
   7407 
   7408     /**
   7409      * Invalidates the region of text enclosed between the start and end text offsets.
   7410      */
   7411     void invalidateRegion(int start, int end, boolean invalidateCursor) {
   7412         if (mLayout == null) {
   7413             invalidate();
   7414         } else {
   7415             int lineStart = mLayout.getLineForOffset(start);
   7416             int top = mLayout.getLineTop(lineStart);
   7417 
   7418             // This is ridiculous, but the descent from the line above
   7419             // can hang down into the line we really want to redraw,
   7420             // so we have to invalidate part of the line above to make
   7421             // sure everything that needs to be redrawn really is.
   7422             // (But not the whole line above, because that would cause
   7423             // the same problem with the descenders on the line above it!)
   7424             if (lineStart > 0) {
   7425                 top -= mLayout.getLineDescent(lineStart - 1);
   7426             }
   7427 
   7428             int lineEnd;
   7429 
   7430             if (start == end) {
   7431                 lineEnd = lineStart;
   7432             } else {
   7433                 lineEnd = mLayout.getLineForOffset(end);
   7434             }
   7435 
   7436             int bottom = mLayout.getLineBottom(lineEnd);
   7437 
   7438             // mEditor can be null in case selection is set programmatically.
   7439             if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
   7440                 final Rect bounds = mEditor.mDrawableForCursor.getBounds();
   7441                 top = Math.min(top, bounds.top);
   7442                 bottom = Math.max(bottom, bounds.bottom);
   7443             }
   7444 
   7445             final int compoundPaddingLeft = getCompoundPaddingLeft();
   7446             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
   7447 
   7448             int left, right;
   7449             if (lineStart == lineEnd && !invalidateCursor) {
   7450                 left = (int) mLayout.getPrimaryHorizontal(start);
   7451                 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
   7452                 left += compoundPaddingLeft;
   7453                 right += compoundPaddingLeft;
   7454             } else {
   7455                 // Rectangle bounding box when the region spans several lines
   7456                 left = compoundPaddingLeft;
   7457                 right = getWidth() - getCompoundPaddingRight();
   7458             }
   7459 
   7460             invalidate(mScrollX + left, verticalPadding + top,
   7461                     mScrollX + right, verticalPadding + bottom);
   7462         }
   7463     }
   7464 
   7465     private void registerForPreDraw() {
   7466         if (!mPreDrawRegistered) {
   7467             getViewTreeObserver().addOnPreDrawListener(this);
   7468             mPreDrawRegistered = true;
   7469         }
   7470     }
   7471 
   7472     private void unregisterForPreDraw() {
   7473         getViewTreeObserver().removeOnPreDrawListener(this);
   7474         mPreDrawRegistered = false;
   7475         mPreDrawListenerDetached = false;
   7476     }
   7477 
   7478     /**
   7479      * {@inheritDoc}
   7480      */
   7481     @Override
   7482     public boolean onPreDraw() {
   7483         if (mLayout == null) {
   7484             assumeLayout();
   7485         }
   7486 
   7487         if (mMovement != null) {
   7488             /* This code also provides auto-scrolling when a cursor is moved using a
   7489              * CursorController (insertion point or selection limits).
   7490              * For selection, ensure start or end is visible depending on controller's state.
   7491              */
   7492             int curs = getSelectionEnd();
   7493             // Do not create the controller if it is not already created.
   7494             if (mEditor != null && mEditor.mSelectionModifierCursorController != null
   7495                     && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
   7496                 curs = getSelectionStart();
   7497             }
   7498 
   7499             /*
   7500              * TODO: This should really only keep the end in view if
   7501              * it already was before the text changed.  I'm not sure
   7502              * of a good way to tell from here if it was.
   7503              */
   7504             if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   7505                 curs = mText.length();
   7506             }
   7507 
   7508             if (curs >= 0) {
   7509                 bringPointIntoView(curs);
   7510             }
   7511         } else {
   7512             bringTextIntoView();
   7513         }
   7514 
   7515         // This has to be checked here since:
   7516         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
   7517         //   a screen rotation) since layout is not yet initialized at that point.
   7518         if (mEditor != null && mEditor.mCreatedWithASelection) {
   7519             mEditor.refreshTextActionMode();
   7520             mEditor.mCreatedWithASelection = false;
   7521         }
   7522 
   7523         unregisterForPreDraw();
   7524 
   7525         return true;
   7526     }
   7527 
   7528     @Override
   7529     protected void onAttachedToWindow() {
   7530         super.onAttachedToWindow();
   7531 
   7532         if (mEditor != null) mEditor.onAttachedToWindow();
   7533 
   7534         if (mPreDrawListenerDetached) {
   7535             getViewTreeObserver().addOnPreDrawListener(this);
   7536             mPreDrawListenerDetached = false;
   7537         }
   7538     }
   7539 
   7540     /** @hide */
   7541     @Override
   7542     protected void onDetachedFromWindowInternal() {
   7543         if (mPreDrawRegistered) {
   7544             getViewTreeObserver().removeOnPreDrawListener(this);
   7545             mPreDrawListenerDetached = true;
   7546         }
   7547 
   7548         resetResolvedDrawables();
   7549 
   7550         if (mEditor != null) mEditor.onDetachedFromWindow();
   7551 
   7552         super.onDetachedFromWindowInternal();
   7553     }
   7554 
   7555     @Override
   7556     public void onScreenStateChanged(int screenState) {
   7557         super.onScreenStateChanged(screenState);
   7558         if (mEditor != null) mEditor.onScreenStateChanged(screenState);
   7559     }
   7560 
   7561     @Override
   7562     protected boolean isPaddingOffsetRequired() {
   7563         return mShadowRadius != 0 || mDrawables != null;
   7564     }
   7565 
   7566     @Override
   7567     protected int getLeftPaddingOffset() {
   7568         return getCompoundPaddingLeft() - mPaddingLeft
   7569                 + (int) Math.min(0, mShadowDx - mShadowRadius);
   7570     }
   7571 
   7572     @Override
   7573     protected int getTopPaddingOffset() {
   7574         return (int) Math.min(0, mShadowDy - mShadowRadius);
   7575     }
   7576 
   7577     @Override
   7578     protected int getBottomPaddingOffset() {
   7579         return (int) Math.max(0, mShadowDy + mShadowRadius);
   7580     }
   7581 
   7582     @Override
   7583     protected int getRightPaddingOffset() {
   7584         return -(getCompoundPaddingRight() - mPaddingRight)
   7585                 + (int) Math.max(0, mShadowDx + mShadowRadius);
   7586     }
   7587 
   7588     @Override
   7589     protected boolean verifyDrawable(@NonNull Drawable who) {
   7590         final boolean verified = super.verifyDrawable(who);
   7591         if (!verified && mDrawables != null) {
   7592             for (Drawable dr : mDrawables.mShowing) {
   7593                 if (who == dr) {
   7594                     return true;
   7595                 }
   7596             }
   7597         }
   7598         return verified;
   7599     }
   7600 
   7601     @Override
   7602     public void jumpDrawablesToCurrentState() {
   7603         super.jumpDrawablesToCurrentState();
   7604         if (mDrawables != null) {
   7605             for (Drawable dr : mDrawables.mShowing) {
   7606                 if (dr != null) {
   7607                     dr.jumpToCurrentState();
   7608                 }
   7609             }
   7610         }
   7611     }
   7612 
   7613     @Override
   7614     public void invalidateDrawable(@NonNull Drawable drawable) {
   7615         boolean handled = false;
   7616 
   7617         if (verifyDrawable(drawable)) {
   7618             final Rect dirty = drawable.getBounds();
   7619             int scrollX = mScrollX;
   7620             int scrollY = mScrollY;
   7621 
   7622             // IMPORTANT: The coordinates below are based on the coordinates computed
   7623             // for each compound drawable in onDraw(). Make sure to update each section
   7624             // accordingly.
   7625             final TextView.Drawables drawables = mDrawables;
   7626             if (drawables != null) {
   7627                 if (drawable == drawables.mShowing[Drawables.LEFT]) {
   7628                     final int compoundPaddingTop = getCompoundPaddingTop();
   7629                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   7630                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   7631 
   7632                     scrollX += mPaddingLeft;
   7633                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
   7634                     handled = true;
   7635                 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
   7636                     final int compoundPaddingTop = getCompoundPaddingTop();
   7637                     final int compoundPaddingBottom = getCompoundPaddingBottom();
   7638                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   7639 
   7640                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
   7641                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
   7642                     handled = true;
   7643                 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
   7644                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   7645                     final int compoundPaddingRight = getCompoundPaddingRight();
   7646                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   7647 
   7648                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
   7649                     scrollY += mPaddingTop;
   7650                     handled = true;
   7651                 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
   7652                     final int compoundPaddingLeft = getCompoundPaddingLeft();
   7653                     final int compoundPaddingRight = getCompoundPaddingRight();
   7654                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
   7655 
   7656                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
   7657                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
   7658                     handled = true;
   7659                 }
   7660             }
   7661 
   7662             if (handled) {
   7663                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
   7664                         dirty.right + scrollX, dirty.bottom + scrollY);
   7665             }
   7666         }
   7667 
   7668         if (!handled) {
   7669             super.invalidateDrawable(drawable);
   7670         }
   7671     }
   7672 
   7673     @Override
   7674     public boolean hasOverlappingRendering() {
   7675         // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
   7676         return ((getBackground() != null && getBackground().getCurrent() != null)
   7677                 || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()
   7678                 || mShadowColor != 0);
   7679     }
   7680 
   7681     /**
   7682      *
   7683      * Returns the state of the {@code textIsSelectable} flag (See
   7684      * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
   7685      * to allow users to select and copy text in a non-editable TextView, the content of an
   7686      * {@link EditText} can always be selected, independently of the value of this flag.
   7687      * <p>
   7688      *
   7689      * @return True if the text displayed in this TextView can be selected by the user.
   7690      *
   7691      * @attr ref android.R.styleable#TextView_textIsSelectable
   7692      */
   7693     @InspectableProperty(name = "textIsSelectable")
   7694     public boolean isTextSelectable() {
   7695         return mEditor == null ? false : mEditor.mTextIsSelectable;
   7696     }
   7697 
   7698     /**
   7699      * Sets whether the content of this view is selectable by the user. The default is
   7700      * {@code false}, meaning that the content is not selectable.
   7701      * <p>
   7702      * When you use a TextView to display a useful piece of information to the user (such as a
   7703      * contact's address), make it selectable, so that the user can select and copy its
   7704      * content. You can also use set the XML attribute
   7705      * {@link android.R.styleable#TextView_textIsSelectable} to "true".
   7706      * <p>
   7707      * When you call this method to set the value of {@code textIsSelectable}, it sets
   7708      * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
   7709      * and {@code longClickable} to the same value. These flags correspond to the attributes
   7710      * {@link android.R.styleable#View_focusable android:focusable},
   7711      * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
   7712      * {@link android.R.styleable#View_clickable android:clickable}, and
   7713      * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
   7714      * flags to a state you had set previously, call one or more of the following methods:
   7715      * {@link #setFocusable(boolean) setFocusable()},
   7716      * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
   7717      * {@link #setClickable(boolean) setClickable()} or
   7718      * {@link #setLongClickable(boolean) setLongClickable()}.
   7719      *
   7720      * @param selectable Whether the content of this TextView should be selectable.
   7721      */
   7722     public void setTextIsSelectable(boolean selectable) {
   7723         if (!selectable && mEditor == null) return; // false is default value with no edit data
   7724 
   7725         createEditorIfNeeded();
   7726         if (mEditor.mTextIsSelectable == selectable) return;
   7727 
   7728         mEditor.mTextIsSelectable = selectable;
   7729         setFocusableInTouchMode(selectable);
   7730         setFocusable(FOCUSABLE_AUTO);
   7731         setClickable(selectable);
   7732         setLongClickable(selectable);
   7733 
   7734         // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
   7735 
   7736         setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
   7737         setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
   7738 
   7739         // Called by setText above, but safer in case of future code changes
   7740         mEditor.prepareCursorControllers();
   7741     }
   7742 
   7743     @Override
   7744     protected int[] onCreateDrawableState(int extraSpace) {
   7745         final int[] drawableState;
   7746 
   7747         if (mSingleLine) {
   7748             drawableState = super.onCreateDrawableState(extraSpace);
   7749         } else {
   7750             drawableState = super.onCreateDrawableState(extraSpace + 1);
   7751             mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
   7752         }
   7753 
   7754         if (isTextSelectable()) {
   7755             // Disable pressed state, which was introduced when TextView was made clickable.
   7756             // Prevents text color change.
   7757             // setClickable(false) would have a similar effect, but it also disables focus changes
   7758             // and long press actions, which are both needed by text selection.
   7759             final int length = drawableState.length;
   7760             for (int i = 0; i < length; i++) {
   7761                 if (drawableState[i] == R.attr.state_pressed) {
   7762                     final int[] nonPressedState = new int[length - 1];
   7763                     System.arraycopy(drawableState, 0, nonPressedState, 0, i);
   7764                     System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
   7765                     return nonPressedState;
   7766                 }
   7767             }
   7768         }
   7769 
   7770         return drawableState;
   7771     }
   7772 
   7773     @UnsupportedAppUsage
   7774     private Path getUpdatedHighlightPath() {
   7775         Path highlight = null;
   7776         Paint highlightPaint = mHighlightPaint;
   7777 
   7778         final int selStart = getSelectionStart();
   7779         final int selEnd = getSelectionEnd();
   7780         if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
   7781             if (selStart == selEnd) {
   7782                 if (mEditor != null && mEditor.shouldRenderCursor()) {
   7783                     if (mHighlightPathBogus) {
   7784                         if (mHighlightPath == null) mHighlightPath = new Path();
   7785                         mHighlightPath.reset();
   7786                         mLayout.getCursorPath(selStart, mHighlightPath, mText);
   7787                         mEditor.updateCursorPosition();
   7788                         mHighlightPathBogus = false;
   7789                     }
   7790 
   7791                     // XXX should pass to skin instead of drawing directly
   7792                     highlightPaint.setColor(mCurTextColor);
   7793                     highlightPaint.setStyle(Paint.Style.STROKE);
   7794                     highlight = mHighlightPath;
   7795                 }
   7796             } else {
   7797                 if (mHighlightPathBogus) {
   7798                     if (mHighlightPath == null) mHighlightPath = new Path();
   7799                     mHighlightPath.reset();
   7800                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   7801                     mHighlightPathBogus = false;
   7802                 }
   7803 
   7804                 // XXX should pass to skin instead of drawing directly
   7805                 highlightPaint.setColor(mHighlightColor);
   7806                 highlightPaint.setStyle(Paint.Style.FILL);
   7807 
   7808                 highlight = mHighlightPath;
   7809             }
   7810         }
   7811         return highlight;
   7812     }
   7813 
   7814     /**
   7815      * @hide
   7816      */
   7817     public int getHorizontalOffsetForDrawables() {
   7818         return 0;
   7819     }
   7820 
   7821     @Override
   7822     protected void onDraw(Canvas canvas) {
   7823         restartMarqueeIfNeeded();
   7824 
   7825         // Draw the background for this view
   7826         super.onDraw(canvas);
   7827 
   7828         final int compoundPaddingLeft = getCompoundPaddingLeft();
   7829         final int compoundPaddingTop = getCompoundPaddingTop();
   7830         final int compoundPaddingRight = getCompoundPaddingRight();
   7831         final int compoundPaddingBottom = getCompoundPaddingBottom();
   7832         final int scrollX = mScrollX;
   7833         final int scrollY = mScrollY;
   7834         final int right = mRight;
   7835         final int left = mLeft;
   7836         final int bottom = mBottom;
   7837         final int top = mTop;
   7838         final boolean isLayoutRtl = isLayoutRtl();
   7839         final int offset = getHorizontalOffsetForDrawables();
   7840         final int leftOffset = isLayoutRtl ? 0 : offset;
   7841         final int rightOffset = isLayoutRtl ? offset : 0;
   7842 
   7843         final Drawables dr = mDrawables;
   7844         if (dr != null) {
   7845             /*
   7846              * Compound, not extended, because the icon is not clipped
   7847              * if the text height is smaller.
   7848              */
   7849 
   7850             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
   7851             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
   7852 
   7853             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   7854             // Make sure to update invalidateDrawable() when changing this code.
   7855             if (dr.mShowing[Drawables.LEFT] != null) {
   7856                 canvas.save();
   7857                 canvas.translate(scrollX + mPaddingLeft + leftOffset,
   7858                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
   7859                 dr.mShowing[Drawables.LEFT].draw(canvas);
   7860                 canvas.restore();
   7861             }
   7862 
   7863             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   7864             // Make sure to update invalidateDrawable() when changing this code.
   7865             if (dr.mShowing[Drawables.RIGHT] != null) {
   7866                 canvas.save();
   7867                 canvas.translate(scrollX + right - left - mPaddingRight
   7868                         - dr.mDrawableSizeRight - rightOffset,
   7869                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
   7870                 dr.mShowing[Drawables.RIGHT].draw(canvas);
   7871                 canvas.restore();
   7872             }
   7873 
   7874             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   7875             // Make sure to update invalidateDrawable() when changing this code.
   7876             if (dr.mShowing[Drawables.TOP] != null) {
   7877                 canvas.save();
   7878                 canvas.translate(scrollX + compoundPaddingLeft
   7879                         + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
   7880                 dr.mShowing[Drawables.TOP].draw(canvas);
   7881                 canvas.restore();
   7882             }
   7883 
   7884             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
   7885             // Make sure to update invalidateDrawable() when changing this code.
   7886             if (dr.mShowing[Drawables.BOTTOM] != null) {
   7887                 canvas.save();
   7888                 canvas.translate(scrollX + compoundPaddingLeft
   7889                         + (hspace - dr.mDrawableWidthBottom) / 2,
   7890                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
   7891                 dr.mShowing[Drawables.BOTTOM].draw(canvas);
   7892                 canvas.restore();
   7893             }
   7894         }
   7895 
   7896         int color = mCurTextColor;
   7897 
   7898         if (mLayout == null) {
   7899             assumeLayout();
   7900         }
   7901 
   7902         Layout layout = mLayout;
   7903 
   7904         if (mHint != null && mText.length() == 0) {
   7905             if (mHintTextColor != null) {
   7906                 color = mCurHintTextColor;
   7907             }
   7908 
   7909             layout = mHintLayout;
   7910         }
   7911 
   7912         mTextPaint.setColor(color);
   7913         mTextPaint.drawableState = getDrawableState();
   7914 
   7915         canvas.save();
   7916         /*  Would be faster if we didn't have to do this. Can we chop the
   7917             (displayable) text so that we don't need to do this ever?
   7918         */
   7919 
   7920         int extendedPaddingTop = getExtendedPaddingTop();
   7921         int extendedPaddingBottom = getExtendedPaddingBottom();
   7922 
   7923         final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
   7924         final int maxScrollY = mLayout.getHeight() - vspace;
   7925 
   7926         float clipLeft = compoundPaddingLeft + scrollX;
   7927         float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
   7928         float clipRight = right - left - getCompoundPaddingRight() + scrollX;
   7929         float clipBottom = bottom - top + scrollY
   7930                 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
   7931 
   7932         if (mShadowRadius != 0) {
   7933             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
   7934             clipRight += Math.max(0, mShadowDx + mShadowRadius);
   7935 
   7936             clipTop += Math.min(0, mShadowDy - mShadowRadius);
   7937             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
   7938         }
   7939 
   7940         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
   7941 
   7942         int voffsetText = 0;
   7943         int voffsetCursor = 0;
   7944 
   7945         // translate in by our padding
   7946         /* shortcircuit calling getVerticaOffset() */
   7947         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   7948             voffsetText = getVerticalOffset(false);
   7949             voffsetCursor = getVerticalOffset(true);
   7950         }
   7951         canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
   7952 
   7953         final int layoutDirection = getLayoutDirection();
   7954         final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
   7955         if (isMarqueeFadeEnabled()) {
   7956             if (!mSingleLine && getLineCount() == 1 && canMarquee()
   7957                     && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
   7958                 final int width = mRight - mLeft;
   7959                 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
   7960                 final float dx = mLayout.getLineRight(0) - (width - padding);
   7961                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   7962             }
   7963 
   7964             if (mMarquee != null && mMarquee.isRunning()) {
   7965                 final float dx = -mMarquee.getScroll();
   7966                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   7967             }
   7968         }
   7969 
   7970         final int cursorOffsetVertical = voffsetCursor - voffsetText;
   7971 
   7972         Path highlight = getUpdatedHighlightPath();
   7973         if (mEditor != null) {
   7974             mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
   7975         } else {
   7976             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
   7977         }
   7978 
   7979         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
   7980             final float dx = mMarquee.getGhostOffset();
   7981             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
   7982             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
   7983         }
   7984 
   7985         canvas.restore();
   7986     }
   7987 
   7988     @Override
   7989     public void getFocusedRect(Rect r) {
   7990         if (mLayout == null) {
   7991             super.getFocusedRect(r);
   7992             return;
   7993         }
   7994 
   7995         int selEnd = getSelectionEnd();
   7996         if (selEnd < 0) {
   7997             super.getFocusedRect(r);
   7998             return;
   7999         }
   8000 
   8001         int selStart = getSelectionStart();
   8002         if (selStart < 0 || selStart >= selEnd) {
   8003             int line = mLayout.getLineForOffset(selEnd);
   8004             r.top = mLayout.getLineTop(line);
   8005             r.bottom = mLayout.getLineBottom(line);
   8006             r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
   8007             r.right = r.left + 4;
   8008         } else {
   8009             int lineStart = mLayout.getLineForOffset(selStart);
   8010             int lineEnd = mLayout.getLineForOffset(selEnd);
   8011             r.top = mLayout.getLineTop(lineStart);
   8012             r.bottom = mLayout.getLineBottom(lineEnd);
   8013             if (lineStart == lineEnd) {
   8014                 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
   8015                 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
   8016             } else {
   8017                 // Selection extends across multiple lines -- make the focused
   8018                 // rect cover the entire width.
   8019                 if (mHighlightPathBogus) {
   8020                     if (mHighlightPath == null) mHighlightPath = new Path();
   8021                     mHighlightPath.reset();
   8022                     mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
   8023                     mHighlightPathBogus = false;
   8024                 }
   8025                 synchronized (TEMP_RECTF) {
   8026                     mHighlightPath.computeBounds(TEMP_RECTF, true);
   8027                     r.left = (int) TEMP_RECTF.left - 1;
   8028                     r.right = (int) TEMP_RECTF.right + 1;
   8029                 }
   8030             }
   8031         }
   8032 
   8033         // Adjust for padding and gravity.
   8034         int paddingLeft = getCompoundPaddingLeft();
   8035         int paddingTop = getExtendedPaddingTop();
   8036         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   8037             paddingTop += getVerticalOffset(false);
   8038         }
   8039         r.offset(paddingLeft, paddingTop);
   8040         int paddingBottom = getExtendedPaddingBottom();
   8041         r.bottom += paddingBottom;
   8042     }
   8043 
   8044     /**
   8045      * Return the number of lines of text, or 0 if the internal Layout has not
   8046      * been built.
   8047      */
   8048     public int getLineCount() {
   8049         return mLayout != null ? mLayout.getLineCount() : 0;
   8050     }
   8051 
   8052     /**
   8053      * Return the baseline for the specified line (0...getLineCount() - 1)
   8054      * If bounds is not null, return the top, left, right, bottom extents
   8055      * of the specified line in it. If the internal Layout has not been built,
   8056      * return 0 and set bounds to (0, 0, 0, 0)
   8057      * @param line which line to examine (0..getLineCount() - 1)
   8058      * @param bounds Optional. If not null, it returns the extent of the line
   8059      * @return the Y-coordinate of the baseline
   8060      */
   8061     public int getLineBounds(int line, Rect bounds) {
   8062         if (mLayout == null) {
   8063             if (bounds != null) {
   8064                 bounds.set(0, 0, 0, 0);
   8065             }
   8066             return 0;
   8067         } else {
   8068             int baseline = mLayout.getLineBounds(line, bounds);
   8069 
   8070             int voffset = getExtendedPaddingTop();
   8071             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   8072                 voffset += getVerticalOffset(true);
   8073             }
   8074             if (bounds != null) {
   8075                 bounds.offset(getCompoundPaddingLeft(), voffset);
   8076             }
   8077             return baseline + voffset;
   8078         }
   8079     }
   8080 
   8081     @Override
   8082     public int getBaseline() {
   8083         if (mLayout == null) {
   8084             return super.getBaseline();
   8085         }
   8086 
   8087         return getBaselineOffset() + mLayout.getLineBaseline(0);
   8088     }
   8089 
   8090     int getBaselineOffset() {
   8091         int voffset = 0;
   8092         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   8093             voffset = getVerticalOffset(true);
   8094         }
   8095 
   8096         if (isLayoutModeOptical(mParent)) {
   8097             voffset -= getOpticalInsets().top;
   8098         }
   8099 
   8100         return getExtendedPaddingTop() + voffset;
   8101     }
   8102 
   8103     /**
   8104      * @hide
   8105      */
   8106     @Override
   8107     protected int getFadeTop(boolean offsetRequired) {
   8108         if (mLayout == null) return 0;
   8109 
   8110         int voffset = 0;
   8111         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   8112             voffset = getVerticalOffset(true);
   8113         }
   8114 
   8115         if (offsetRequired) voffset += getTopPaddingOffset();
   8116 
   8117         return getExtendedPaddingTop() + voffset;
   8118     }
   8119 
   8120     /**
   8121      * @hide
   8122      */
   8123     @Override
   8124     protected int getFadeHeight(boolean offsetRequired) {
   8125         return mLayout != null ? mLayout.getHeight() : 0;
   8126     }
   8127 
   8128     @Override
   8129     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
   8130         if (mSpannable != null && mLinksClickable) {
   8131             final float x = event.getX(pointerIndex);
   8132             final float y = event.getY(pointerIndex);
   8133             final int offset = getOffsetForPosition(x, y);
   8134             final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
   8135                     ClickableSpan.class);
   8136             if (clickables.length > 0) {
   8137                 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
   8138             }
   8139         }
   8140         if (isTextSelectable() || isTextEditable()) {
   8141             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
   8142         }
   8143         return super.onResolvePointerIcon(event, pointerIndex);
   8144     }
   8145 
   8146     @Override
   8147     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
   8148         // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
   8149         // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
   8150         // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
   8151         if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
   8152             return true;
   8153         }
   8154         return super.onKeyPreIme(keyCode, event);
   8155     }
   8156 
   8157     /**
   8158      * @hide
   8159      */
   8160     public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
   8161         // Do nothing unless mEditor is in text action mode.
   8162         if (mEditor == null || mEditor.getTextActionMode() == null) {
   8163             return false;
   8164         }
   8165 
   8166         if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
   8167             KeyEvent.DispatcherState state = getKeyDispatcherState();
   8168             if (state != null) {
   8169                 state.startTracking(event, this);
   8170             }
   8171             return true;
   8172         } else if (event.getAction() == KeyEvent.ACTION_UP) {
   8173             KeyEvent.DispatcherState state = getKeyDispatcherState();
   8174             if (state != null) {
   8175                 state.handleUpEvent(event);
   8176             }
   8177             if (event.isTracking() && !event.isCanceled()) {
   8178                 stopTextActionMode();
   8179                 return true;
   8180             }
   8181         }
   8182         return false;
   8183     }
   8184 
   8185     @Override
   8186     public boolean onKeyDown(int keyCode, KeyEvent event) {
   8187         final int which = doKeyDown(keyCode, event, null);
   8188         if (which == KEY_EVENT_NOT_HANDLED) {
   8189             return super.onKeyDown(keyCode, event);
   8190         }
   8191 
   8192         return true;
   8193     }
   8194 
   8195     @Override
   8196     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
   8197         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
   8198         final int which = doKeyDown(keyCode, down, event);
   8199         if (which == KEY_EVENT_NOT_HANDLED) {
   8200             // Go through default dispatching.
   8201             return super.onKeyMultiple(keyCode, repeatCount, event);
   8202         }
   8203         if (which == KEY_EVENT_HANDLED) {
   8204             // Consumed the whole thing.
   8205             return true;
   8206         }
   8207 
   8208         repeatCount--;
   8209 
   8210         // We are going to dispatch the remaining events to either the input
   8211         // or movement method.  To do this, we will just send a repeated stream
   8212         // of down and up events until we have done the complete repeatCount.
   8213         // It would be nice if those interfaces had an onKeyMultiple() method,
   8214         // but adding that is a more complicated change.
   8215         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
   8216         if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
   8217             // mEditor and mEditor.mInput are not null from doKeyDown
   8218             mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
   8219             while (--repeatCount > 0) {
   8220                 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
   8221                 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
   8222             }
   8223             hideErrorIfUnchanged();
   8224 
   8225         } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
   8226             // mMovement is not null from doKeyDown
   8227             mMovement.onKeyUp(this, mSpannable, keyCode, up);
   8228             while (--repeatCount > 0) {
   8229                 mMovement.onKeyDown(this, mSpannable, keyCode, down);
   8230                 mMovement.onKeyUp(this, mSpannable, keyCode, up);
   8231             }
   8232         }
   8233 
   8234         return true;
   8235     }
   8236 
   8237     /**
   8238      * Returns true if pressing ENTER in this field advances focus instead
   8239      * of inserting the character.  This is true mostly in single-line fields,
   8240      * but also in mail addresses and subjects which will display on multiple
   8241      * lines but where it doesn't make sense to insert newlines.
   8242      */
   8243     private boolean shouldAdvanceFocusOnEnter() {
   8244         if (getKeyListener() == null) {
   8245             return false;
   8246         }
   8247 
   8248         if (mSingleLine) {
   8249             return true;
   8250         }
   8251 
   8252         if (mEditor != null
   8253                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
   8254                         == EditorInfo.TYPE_CLASS_TEXT) {
   8255             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   8256             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
   8257                     || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
   8258                 return true;
   8259             }
   8260         }
   8261 
   8262         return false;
   8263     }
   8264 
   8265     /**
   8266      * Returns true if pressing TAB in this field advances focus instead
   8267      * of inserting the character.  Insert tabs only in multi-line editors.
   8268      */
   8269     private boolean shouldAdvanceFocusOnTab() {
   8270         if (getKeyListener() != null && !mSingleLine && mEditor != null
   8271                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
   8272                         == EditorInfo.TYPE_CLASS_TEXT) {
   8273             int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   8274             if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
   8275                     || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
   8276                 return false;
   8277             }
   8278         }
   8279         return true;
   8280     }
   8281 
   8282     private boolean isDirectionalNavigationKey(int keyCode) {
   8283         switch(keyCode) {
   8284             case KeyEvent.KEYCODE_DPAD_UP:
   8285             case KeyEvent.KEYCODE_DPAD_DOWN:
   8286             case KeyEvent.KEYCODE_DPAD_LEFT:
   8287             case KeyEvent.KEYCODE_DPAD_RIGHT:
   8288                 return true;
   8289         }
   8290         return false;
   8291     }
   8292 
   8293     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
   8294         if (!isEnabled()) {
   8295             return KEY_EVENT_NOT_HANDLED;
   8296         }
   8297 
   8298         // If this is the initial keydown, we don't want to prevent a movement away from this view.
   8299         // While this shouldn't be necessary because any time we're preventing default movement we
   8300         // should be restricting the focus to remain within this view, thus we'll also receive
   8301         // the key up event, occasionally key up events will get dropped and we don't want to
   8302         // prevent the user from traversing out of this on the next key down.
   8303         if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
   8304             mPreventDefaultMovement = false;
   8305         }
   8306 
   8307         switch (keyCode) {
   8308             case KeyEvent.KEYCODE_ENTER:
   8309                 if (event.hasNoModifiers()) {
   8310                     // When mInputContentType is set, we know that we are
   8311                     // running in a "modern" cupcake environment, so don't need
   8312                     // to worry about the application trying to capture
   8313                     // enter key events.
   8314                     if (mEditor != null && mEditor.mInputContentType != null) {
   8315                         // If there is an action listener, given them a
   8316                         // chance to consume the event.
   8317                         if (mEditor.mInputContentType.onEditorActionListener != null
   8318                                 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
   8319                                         this, EditorInfo.IME_NULL, event)) {
   8320                             mEditor.mInputContentType.enterDown = true;
   8321                             // We are consuming the enter key for them.
   8322                             return KEY_EVENT_HANDLED;
   8323                         }
   8324                     }
   8325 
   8326                     // If our editor should move focus when enter is pressed, or
   8327                     // this is a generated event from an IME action button, then
   8328                     // don't let it be inserted into the text.
   8329                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
   8330                             || shouldAdvanceFocusOnEnter()) {
   8331                         if (hasOnClickListeners()) {
   8332                             return KEY_EVENT_NOT_HANDLED;
   8333                         }
   8334                         return KEY_EVENT_HANDLED;
   8335                     }
   8336                 }
   8337                 break;
   8338 
   8339             case KeyEvent.KEYCODE_DPAD_CENTER:
   8340                 if (event.hasNoModifiers()) {
   8341                     if (shouldAdvanceFocusOnEnter()) {
   8342                         return KEY_EVENT_NOT_HANDLED;
   8343                     }
   8344                 }
   8345                 break;
   8346 
   8347             case KeyEvent.KEYCODE_TAB:
   8348                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
   8349                     if (shouldAdvanceFocusOnTab()) {
   8350                         return KEY_EVENT_NOT_HANDLED;
   8351                     }
   8352                 }
   8353                 break;
   8354 
   8355                 // Has to be done on key down (and not on key up) to correctly be intercepted.
   8356             case KeyEvent.KEYCODE_BACK:
   8357                 if (mEditor != null && mEditor.getTextActionMode() != null) {
   8358                     stopTextActionMode();
   8359                     return KEY_EVENT_HANDLED;
   8360                 }
   8361                 break;
   8362 
   8363             case KeyEvent.KEYCODE_CUT:
   8364                 if (event.hasNoModifiers() && canCut()) {
   8365                     if (onTextContextMenuItem(ID_CUT)) {
   8366                         return KEY_EVENT_HANDLED;
   8367                     }
   8368                 }
   8369                 break;
   8370 
   8371             case KeyEvent.KEYCODE_COPY:
   8372                 if (event.hasNoModifiers() && canCopy()) {
   8373                     if (onTextContextMenuItem(ID_COPY)) {
   8374                         return KEY_EVENT_HANDLED;
   8375                     }
   8376                 }
   8377                 break;
   8378 
   8379             case KeyEvent.KEYCODE_PASTE:
   8380                 if (event.hasNoModifiers() && canPaste()) {
   8381                     if (onTextContextMenuItem(ID_PASTE)) {
   8382                         return KEY_EVENT_HANDLED;
   8383                     }
   8384                 }
   8385                 break;
   8386 
   8387             case KeyEvent.KEYCODE_FORWARD_DEL:
   8388                 if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canCut()) {
   8389                     if (onTextContextMenuItem(ID_CUT)) {
   8390                         return KEY_EVENT_HANDLED;
   8391                     }
   8392                 }
   8393                 break;
   8394 
   8395             case KeyEvent.KEYCODE_INSERT:
   8396                 if (event.hasModifiers(KeyEvent.META_CTRL_ON) && canCopy()) {
   8397                     if (onTextContextMenuItem(ID_COPY)) {
   8398                         return KEY_EVENT_HANDLED;
   8399                     }
   8400                 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON) && canPaste()) {
   8401                     if (onTextContextMenuItem(ID_PASTE)) {
   8402                         return KEY_EVENT_HANDLED;
   8403                     }
   8404                 }
   8405                 break;
   8406         }
   8407 
   8408         if (mEditor != null && mEditor.mKeyListener != null) {
   8409             boolean doDown = true;
   8410             if (otherEvent != null) {
   8411                 try {
   8412                     beginBatchEdit();
   8413                     final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
   8414                             otherEvent);
   8415                     hideErrorIfUnchanged();
   8416                     doDown = false;
   8417                     if (handled) {
   8418                         return KEY_EVENT_HANDLED;
   8419                     }
   8420                 } catch (AbstractMethodError e) {
   8421                     // onKeyOther was added after 1.0, so if it isn't
   8422                     // implemented we need to try to dispatch as a regular down.
   8423                 } finally {
   8424                     endBatchEdit();
   8425                 }
   8426             }
   8427 
   8428             if (doDown) {
   8429                 beginBatchEdit();
   8430                 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
   8431                         keyCode, event);
   8432                 endBatchEdit();
   8433                 hideErrorIfUnchanged();
   8434                 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
   8435             }
   8436         }
   8437 
   8438         // bug 650865: sometimes we get a key event before a layout.
   8439         // don't try to move around if we don't know the layout.
   8440 
   8441         if (mMovement != null && mLayout != null) {
   8442             boolean doDown = true;
   8443             if (otherEvent != null) {
   8444                 try {
   8445                     boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
   8446                     doDown = false;
   8447                     if (handled) {
   8448                         return KEY_EVENT_HANDLED;
   8449                     }
   8450                 } catch (AbstractMethodError e) {
   8451                     // onKeyOther was added after 1.0, so if it isn't
   8452                     // implemented we need to try to dispatch as a regular down.
   8453                 }
   8454             }
   8455             if (doDown) {
   8456                 if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
   8457                     if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
   8458                         mPreventDefaultMovement = true;
   8459                     }
   8460                     return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
   8461                 }
   8462             }
   8463             // Consume arrows from keyboard devices to prevent focus leaving the editor.
   8464             // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
   8465             // to move focus with arrows.
   8466             if (event.getSource() == InputDevice.SOURCE_KEYBOARD
   8467                     && isDirectionalNavigationKey(keyCode)) {
   8468                 return KEY_EVENT_HANDLED;
   8469             }
   8470         }
   8471 
   8472         return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
   8473                 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
   8474     }
   8475 
   8476     /**
   8477      * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
   8478      * can be recorded.
   8479      * @hide
   8480      */
   8481     public void resetErrorChangedFlag() {
   8482         /*
   8483          * Keep track of what the error was before doing the input
   8484          * so that if an input filter changed the error, we leave
   8485          * that error showing.  Otherwise, we take down whatever
   8486          * error was showing when the user types something.
   8487          */
   8488         if (mEditor != null) mEditor.mErrorWasChanged = false;
   8489     }
   8490 
   8491     /**
   8492      * @hide
   8493      */
   8494     public void hideErrorIfUnchanged() {
   8495         if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
   8496             setError(null, null);
   8497         }
   8498     }
   8499 
   8500     @Override
   8501     public boolean onKeyUp(int keyCode, KeyEvent event) {
   8502         if (!isEnabled()) {
   8503             return super.onKeyUp(keyCode, event);
   8504         }
   8505 
   8506         if (!KeyEvent.isModifierKey(keyCode)) {
   8507             mPreventDefaultMovement = false;
   8508         }
   8509 
   8510         switch (keyCode) {
   8511             case KeyEvent.KEYCODE_DPAD_CENTER:
   8512                 if (event.hasNoModifiers()) {
   8513                     /*
   8514                      * If there is a click listener, just call through to
   8515                      * super, which will invoke it.
   8516                      *
   8517                      * If there isn't a click listener, try to show the soft
   8518                      * input method.  (It will also
   8519                      * call performClick(), but that won't do anything in
   8520                      * this case.)
   8521                      */
   8522                     if (!hasOnClickListeners()) {
   8523                         if (mMovement != null && mText instanceof Editable
   8524                                 && mLayout != null && onCheckIsTextEditor()) {
   8525                             InputMethodManager imm = getInputMethodManager();
   8526                             viewClicked(imm);
   8527                             if (imm != null && getShowSoftInputOnFocus()) {
   8528                                 imm.showSoftInput(this, 0);
   8529                             }
   8530                         }
   8531                     }
   8532                 }
   8533                 return super.onKeyUp(keyCode, event);
   8534 
   8535             case KeyEvent.KEYCODE_ENTER:
   8536                 if (event.hasNoModifiers()) {
   8537                     if (mEditor != null && mEditor.mInputContentType != null
   8538                             && mEditor.mInputContentType.onEditorActionListener != null
   8539                             && mEditor.mInputContentType.enterDown) {
   8540                         mEditor.mInputContentType.enterDown = false;
   8541                         if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
   8542                                 this, EditorInfo.IME_NULL, event)) {
   8543                             return true;
   8544                         }
   8545                     }
   8546 
   8547                     if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
   8548                             || shouldAdvanceFocusOnEnter()) {
   8549                         /*
   8550                          * If there is a click listener, just call through to
   8551                          * super, which will invoke it.
   8552                          *
   8553                          * If there isn't a click listener, try to advance focus,
   8554                          * but still call through to super, which will reset the
   8555                          * pressed state and longpress state.  (It will also
   8556                          * call performClick(), but that won't do anything in
   8557                          * this case.)
   8558                          */
   8559                         if (!hasOnClickListeners()) {
   8560                             View v = focusSearch(FOCUS_DOWN);
   8561 
   8562                             if (v != null) {
   8563                                 if (!v.requestFocus(FOCUS_DOWN)) {
   8564                                     throw new IllegalStateException("focus search returned a view "
   8565                                             + "that wasn't able to take focus!");
   8566                                 }
   8567 
   8568                                 /*
   8569                                  * Return true because we handled the key; super
   8570                                  * will return false because there was no click
   8571                                  * listener.
   8572                                  */
   8573                                 super.onKeyUp(keyCode, event);
   8574                                 return true;
   8575                             } else if ((event.getFlags()
   8576                                     & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
   8577                                 // No target for next focus, but make sure the IME
   8578                                 // if this came from it.
   8579                                 InputMethodManager imm = getInputMethodManager();
   8580                                 if (imm != null && imm.isActive(this)) {
   8581                                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
   8582                                 }
   8583                             }
   8584                         }
   8585                     }
   8586                     return super.onKeyUp(keyCode, event);
   8587                 }
   8588                 break;
   8589         }
   8590 
   8591         if (mEditor != null && mEditor.mKeyListener != null) {
   8592             if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
   8593                 return true;
   8594             }
   8595         }
   8596 
   8597         if (mMovement != null && mLayout != null) {
   8598             if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
   8599                 return true;
   8600             }
   8601         }
   8602 
   8603         return super.onKeyUp(keyCode, event);
   8604     }
   8605 
   8606     @Override
   8607     public boolean onCheckIsTextEditor() {
   8608         return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
   8609     }
   8610 
   8611     @Override
   8612     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   8613         if (onCheckIsTextEditor() && isEnabled()) {
   8614             mEditor.createInputMethodStateIfNeeded();
   8615             outAttrs.inputType = getInputType();
   8616             if (mEditor.mInputContentType != null) {
   8617                 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
   8618                 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
   8619                 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
   8620                 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
   8621                 outAttrs.extras = mEditor.mInputContentType.extras;
   8622                 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
   8623             } else {
   8624                 outAttrs.imeOptions = EditorInfo.IME_NULL;
   8625                 outAttrs.hintLocales = null;
   8626             }
   8627             if (focusSearch(FOCUS_DOWN) != null) {
   8628                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
   8629             }
   8630             if (focusSearch(FOCUS_UP) != null) {
   8631                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
   8632             }
   8633             if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
   8634                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
   8635                 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
   8636                     // An action has not been set, but the enter key will move to
   8637                     // the next focus, so set the action to that.
   8638                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
   8639                 } else {
   8640                     // An action has not been set, and there is no focus to move
   8641                     // to, so let's just supply a "done" action.
   8642                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
   8643                 }
   8644                 if (!shouldAdvanceFocusOnEnter()) {
   8645                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   8646                 }
   8647             }
   8648             if (isMultilineInputType(outAttrs.inputType)) {
   8649                 // Multi-line text editors should always show an enter key.
   8650                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
   8651             }
   8652             outAttrs.hintText = mHint;
   8653             outAttrs.targetInputMethodUser = mTextOperationUser;
   8654             if (mText instanceof Editable) {
   8655                 InputConnection ic = new EditableInputConnection(this);
   8656                 outAttrs.initialSelStart = getSelectionStart();
   8657                 outAttrs.initialSelEnd = getSelectionEnd();
   8658                 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
   8659                 return ic;
   8660             }
   8661         }
   8662         return null;
   8663     }
   8664 
   8665     /**
   8666      * If this TextView contains editable content, extract a portion of it
   8667      * based on the information in <var>request</var> in to <var>outText</var>.
   8668      * @return Returns true if the text was successfully extracted, else false.
   8669      */
   8670     public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
   8671         createEditorIfNeeded();
   8672         return mEditor.extractText(request, outText);
   8673     }
   8674 
   8675     /**
   8676      * This is used to remove all style-impacting spans from text before new
   8677      * extracted text is being replaced into it, so that we don't have any
   8678      * lingering spans applied during the replace.
   8679      */
   8680     static void removeParcelableSpans(Spannable spannable, int start, int end) {
   8681         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
   8682         int i = spans.length;
   8683         while (i > 0) {
   8684             i--;
   8685             spannable.removeSpan(spans[i]);
   8686         }
   8687     }
   8688 
   8689     /**
   8690      * Apply to this text view the given extracted text, as previously
   8691      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
   8692      */
   8693     public void setExtractedText(ExtractedText text) {
   8694         Editable content = getEditableText();
   8695         if (text.text != null) {
   8696             if (content == null) {
   8697                 setText(text.text, TextView.BufferType.EDITABLE);
   8698             } else {
   8699                 int start = 0;
   8700                 int end = content.length();
   8701 
   8702                 if (text.partialStartOffset >= 0) {
   8703                     final int N = content.length();
   8704                     start = text.partialStartOffset;
   8705                     if (start > N) start = N;
   8706                     end = text.partialEndOffset;
   8707                     if (end > N) end = N;
   8708                 }
   8709 
   8710                 removeParcelableSpans(content, start, end);
   8711                 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
   8712                     if (text.text instanceof Spanned) {
   8713                         // OK to copy spans only.
   8714                         TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
   8715                                 Object.class, content, start);
   8716                     }
   8717                 } else {
   8718                     content.replace(start, end, text.text);
   8719                 }
   8720             }
   8721         }
   8722 
   8723         // Now set the selection position...  make sure it is in range, to
   8724         // avoid crashes.  If this is a partial update, it is possible that
   8725         // the underlying text may have changed, causing us problems here.
   8726         // Also we just don't want to trust clients to do the right thing.
   8727         Spannable sp = (Spannable) getText();
   8728         final int N = sp.length();
   8729         int start = text.selectionStart;
   8730         if (start < 0) {
   8731             start = 0;
   8732         } else if (start > N) {
   8733             start = N;
   8734         }
   8735         int end = text.selectionEnd;
   8736         if (end < 0) {
   8737             end = 0;
   8738         } else if (end > N) {
   8739             end = N;
   8740         }
   8741         Selection.setSelection(sp, start, end);
   8742 
   8743         // Finally, update the selection mode.
   8744         if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
   8745             MetaKeyKeyListener.startSelecting(this, sp);
   8746         } else {
   8747             MetaKeyKeyListener.stopSelecting(this, sp);
   8748         }
   8749 
   8750         setHintInternal(text.hint);
   8751     }
   8752 
   8753     /**
   8754      * @hide
   8755      */
   8756     public void setExtracting(ExtractedTextRequest req) {
   8757         if (mEditor.mInputMethodState != null) {
   8758             mEditor.mInputMethodState.mExtractedTextRequest = req;
   8759         }
   8760         // This would stop a possible selection mode, but no such mode is started in case
   8761         // extracted mode will start. Some text is selected though, and will trigger an action mode
   8762         // in the extracted view.
   8763         mEditor.hideCursorAndSpanControllers();
   8764         stopTextActionMode();
   8765         if (mEditor.mSelectionModifierCursorController != null) {
   8766             mEditor.mSelectionModifierCursorController.resetTouchOffsets();
   8767         }
   8768     }
   8769 
   8770     /**
   8771      * Called by the framework in response to a text completion from
   8772      * the current input method, provided by it calling
   8773      * {@link InputConnection#commitCompletion
   8774      * InputConnection.commitCompletion()}.  The default implementation does
   8775      * nothing; text views that are supporting auto-completion should override
   8776      * this to do their desired behavior.
   8777      *
   8778      * @param text The auto complete text the user has selected.
   8779      */
   8780     public void onCommitCompletion(CompletionInfo text) {
   8781         // intentionally empty
   8782     }
   8783 
   8784     /**
   8785      * Called by the framework in response to a text auto-correction (such as fixing a typo using a
   8786      * dictionary) from the current input method, provided by it calling
   8787      * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
   8788      * The default implementation flashes the background of the corrected word to provide
   8789      * feedback to the user.
   8790      *
   8791      * @param info The auto correct info about the text that was corrected.
   8792      */
   8793     public void onCommitCorrection(CorrectionInfo info) {
   8794         if (mEditor != null) mEditor.onCommitCorrection(info);
   8795     }
   8796 
   8797     public void beginBatchEdit() {
   8798         if (mEditor != null) mEditor.beginBatchEdit();
   8799     }
   8800 
   8801     public void endBatchEdit() {
   8802         if (mEditor != null) mEditor.endBatchEdit();
   8803     }
   8804 
   8805     /**
   8806      * Called by the framework in response to a request to begin a batch
   8807      * of edit operations through a call to link {@link #beginBatchEdit()}.
   8808      */
   8809     public void onBeginBatchEdit() {
   8810         // intentionally empty
   8811     }
   8812 
   8813     /**
   8814      * Called by the framework in response to a request to end a batch
   8815      * of edit operations through a call to link {@link #endBatchEdit}.
   8816      */
   8817     public void onEndBatchEdit() {
   8818         // intentionally empty
   8819     }
   8820 
   8821     /**
   8822      * Called by the framework in response to a private command from the
   8823      * current method, provided by it calling
   8824      * {@link InputConnection#performPrivateCommand
   8825      * InputConnection.performPrivateCommand()}.
   8826      *
   8827      * @param action The action name of the command.
   8828      * @param data Any additional data for the command.  This may be null.
   8829      * @return Return true if you handled the command, else false.
   8830      */
   8831     public boolean onPrivateIMECommand(String action, Bundle data) {
   8832         return false;
   8833     }
   8834 
   8835     /** @hide */
   8836     @VisibleForTesting
   8837     @UnsupportedAppUsage
   8838     public void nullLayouts() {
   8839         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
   8840             mSavedLayout = (BoringLayout) mLayout;
   8841         }
   8842         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
   8843             mSavedHintLayout = (BoringLayout) mHintLayout;
   8844         }
   8845 
   8846         mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
   8847 
   8848         mBoring = mHintBoring = null;
   8849 
   8850         // Since it depends on the value of mLayout
   8851         if (mEditor != null) mEditor.prepareCursorControllers();
   8852     }
   8853 
   8854     /**
   8855      * Make a new Layout based on the already-measured size of the view,
   8856      * on the assumption that it was measured correctly at some point.
   8857      */
   8858     @UnsupportedAppUsage
   8859     private void assumeLayout() {
   8860         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   8861 
   8862         if (width < 1) {
   8863             width = 0;
   8864         }
   8865 
   8866         int physicalWidth = width;
   8867 
   8868         if (mHorizontallyScrolling) {
   8869             width = VERY_WIDE;
   8870         }
   8871 
   8872         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
   8873                       physicalWidth, false);
   8874     }
   8875 
   8876     @UnsupportedAppUsage
   8877     private Layout.Alignment getLayoutAlignment() {
   8878         Layout.Alignment alignment;
   8879         switch (getTextAlignment()) {
   8880             case TEXT_ALIGNMENT_GRAVITY:
   8881                 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
   8882                     case Gravity.START:
   8883                         alignment = Layout.Alignment.ALIGN_NORMAL;
   8884                         break;
   8885                     case Gravity.END:
   8886                         alignment = Layout.Alignment.ALIGN_OPPOSITE;
   8887                         break;
   8888                     case Gravity.LEFT:
   8889                         alignment = Layout.Alignment.ALIGN_LEFT;
   8890                         break;
   8891                     case Gravity.RIGHT:
   8892                         alignment = Layout.Alignment.ALIGN_RIGHT;
   8893                         break;
   8894                     case Gravity.CENTER_HORIZONTAL:
   8895                         alignment = Layout.Alignment.ALIGN_CENTER;
   8896                         break;
   8897                     default:
   8898                         alignment = Layout.Alignment.ALIGN_NORMAL;
   8899                         break;
   8900                 }
   8901                 break;
   8902             case TEXT_ALIGNMENT_TEXT_START:
   8903                 alignment = Layout.Alignment.ALIGN_NORMAL;
   8904                 break;
   8905             case TEXT_ALIGNMENT_TEXT_END:
   8906                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
   8907                 break;
   8908             case TEXT_ALIGNMENT_CENTER:
   8909                 alignment = Layout.Alignment.ALIGN_CENTER;
   8910                 break;
   8911             case TEXT_ALIGNMENT_VIEW_START:
   8912                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
   8913                         ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
   8914                 break;
   8915             case TEXT_ALIGNMENT_VIEW_END:
   8916                 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
   8917                         ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
   8918                 break;
   8919             case TEXT_ALIGNMENT_INHERIT:
   8920                 // This should never happen as we have already resolved the text alignment
   8921                 // but better safe than sorry so we just fall through
   8922             default:
   8923                 alignment = Layout.Alignment.ALIGN_NORMAL;
   8924                 break;
   8925         }
   8926         return alignment;
   8927     }
   8928 
   8929     /**
   8930      * The width passed in is now the desired layout width,
   8931      * not the full view width with padding.
   8932      * {@hide}
   8933      */
   8934     @VisibleForTesting
   8935     @UnsupportedAppUsage
   8936     public void makeNewLayout(int wantWidth, int hintWidth,
   8937                                  BoringLayout.Metrics boring,
   8938                                  BoringLayout.Metrics hintBoring,
   8939                                  int ellipsisWidth, boolean bringIntoView) {
   8940         stopMarquee();
   8941 
   8942         // Update "old" cached values
   8943         mOldMaximum = mMaximum;
   8944         mOldMaxMode = mMaxMode;
   8945 
   8946         mHighlightPathBogus = true;
   8947 
   8948         if (wantWidth < 0) {
   8949             wantWidth = 0;
   8950         }
   8951         if (hintWidth < 0) {
   8952             hintWidth = 0;
   8953         }
   8954 
   8955         Layout.Alignment alignment = getLayoutAlignment();
   8956         final boolean testDirChange = mSingleLine && mLayout != null
   8957                 && (alignment == Layout.Alignment.ALIGN_NORMAL
   8958                         || alignment == Layout.Alignment.ALIGN_OPPOSITE);
   8959         int oldDir = 0;
   8960         if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
   8961         boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
   8962         final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
   8963                 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
   8964         TruncateAt effectiveEllipsize = mEllipsize;
   8965         if (mEllipsize == TruncateAt.MARQUEE
   8966                 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   8967             effectiveEllipsize = TruncateAt.END_SMALL;
   8968         }
   8969 
   8970         if (mTextDir == null) {
   8971             mTextDir = getTextDirectionHeuristic();
   8972         }
   8973 
   8974         mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
   8975                 effectiveEllipsize, effectiveEllipsize == mEllipsize);
   8976         if (switchEllipsize) {
   8977             TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
   8978                     ? TruncateAt.END : TruncateAt.MARQUEE;
   8979             mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
   8980                     shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
   8981         }
   8982 
   8983         shouldEllipsize = mEllipsize != null;
   8984         mHintLayout = null;
   8985 
   8986         if (mHint != null) {
   8987             if (shouldEllipsize) hintWidth = wantWidth;
   8988 
   8989             if (hintBoring == UNKNOWN_BORING) {
   8990                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
   8991                                                    mHintBoring);
   8992                 if (hintBoring != null) {
   8993                     mHintBoring = hintBoring;
   8994                 }
   8995             }
   8996 
   8997             if (hintBoring != null) {
   8998                 if (hintBoring.width <= hintWidth
   8999                         && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
   9000                     if (mSavedHintLayout != null) {
   9001                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
   9002                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   9003                                 hintBoring, mIncludePad);
   9004                     } else {
   9005                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   9006                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   9007                                 hintBoring, mIncludePad);
   9008                     }
   9009 
   9010                     mSavedHintLayout = (BoringLayout) mHintLayout;
   9011                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
   9012                     if (mSavedHintLayout != null) {
   9013                         mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
   9014                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   9015                                 hintBoring, mIncludePad, mEllipsize,
   9016                                 ellipsisWidth);
   9017                     } else {
   9018                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
   9019                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
   9020                                 hintBoring, mIncludePad, mEllipsize,
   9021                                 ellipsisWidth);
   9022                     }
   9023                 }
   9024             }
   9025             // TODO: code duplication with makeSingleLayout()
   9026             if (mHintLayout == null) {
   9027                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
   9028                         mHint.length(), mTextPaint, hintWidth)
   9029                         .setAlignment(alignment)
   9030                         .setTextDirection(mTextDir)
   9031                         .setLineSpacing(mSpacingAdd, mSpacingMult)
   9032                         .setIncludePad(mIncludePad)
   9033                         .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
   9034                         .setBreakStrategy(mBreakStrategy)
   9035                         .setHyphenationFrequency(mHyphenationFrequency)
   9036                         .setJustificationMode(mJustificationMode)
   9037                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
   9038                 if (shouldEllipsize) {
   9039                     builder.setEllipsize(mEllipsize)
   9040                             .setEllipsizedWidth(ellipsisWidth);
   9041                 }
   9042                 mHintLayout = builder.build();
   9043             }
   9044         }
   9045 
   9046         if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
   9047             registerForPreDraw();
   9048         }
   9049 
   9050         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   9051             if (!compressText(ellipsisWidth)) {
   9052                 final int height = mLayoutParams.height;
   9053                 // If the size of the view does not depend on the size of the text, try to
   9054                 // start the marquee immediately
   9055                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
   9056                     startMarquee();
   9057                 } else {
   9058                     // Defer the start of the marquee until we know our width (see setFrame())
   9059                     mRestartMarquee = true;
   9060                 }
   9061             }
   9062         }
   9063 
   9064         // CursorControllers need a non-null mLayout
   9065         if (mEditor != null) mEditor.prepareCursorControllers();
   9066     }
   9067 
   9068     /**
   9069      * Returns true if DynamicLayout is required
   9070      *
   9071      * @hide
   9072      */
   9073     @VisibleForTesting
   9074     public boolean useDynamicLayout() {
   9075         return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
   9076     }
   9077 
   9078     /**
   9079      * @hide
   9080      */
   9081     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
   9082             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
   9083             boolean useSaved) {
   9084         Layout result = null;
   9085         if (useDynamicLayout()) {
   9086             final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
   9087                     wantWidth)
   9088                     .setDisplayText(mTransformed)
   9089                     .setAlignment(alignment)
   9090                     .setTextDirection(mTextDir)
   9091                     .setLineSpacing(mSpacingAdd, mSpacingMult)
   9092                     .setIncludePad(mIncludePad)
   9093                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
   9094                     .setBreakStrategy(mBreakStrategy)
   9095                     .setHyphenationFrequency(mHyphenationFrequency)
   9096                     .setJustificationMode(mJustificationMode)
   9097                     .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
   9098                     .setEllipsizedWidth(ellipsisWidth);
   9099             result = builder.build();
   9100         } else {
   9101             if (boring == UNKNOWN_BORING) {
   9102                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
   9103                 if (boring != null) {
   9104                     mBoring = boring;
   9105                 }
   9106             }
   9107 
   9108             if (boring != null) {
   9109                 if (boring.width <= wantWidth
   9110                         && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
   9111                     if (useSaved && mSavedLayout != null) {
   9112                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
   9113                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   9114                                 boring, mIncludePad);
   9115                     } else {
   9116                         result = BoringLayout.make(mTransformed, mTextPaint,
   9117                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   9118                                 boring, mIncludePad);
   9119                     }
   9120 
   9121                     if (useSaved) {
   9122                         mSavedLayout = (BoringLayout) result;
   9123                     }
   9124                 } else if (shouldEllipsize && boring.width <= wantWidth) {
   9125                     if (useSaved && mSavedLayout != null) {
   9126                         result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
   9127                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   9128                                 boring, mIncludePad, effectiveEllipsize,
   9129                                 ellipsisWidth);
   9130                     } else {
   9131                         result = BoringLayout.make(mTransformed, mTextPaint,
   9132                                 wantWidth, alignment, mSpacingMult, mSpacingAdd,
   9133                                 boring, mIncludePad, effectiveEllipsize,
   9134                                 ellipsisWidth);
   9135                     }
   9136                 }
   9137             }
   9138         }
   9139         if (result == null) {
   9140             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
   9141                     0, mTransformed.length(), mTextPaint, wantWidth)
   9142                     .setAlignment(alignment)
   9143                     .setTextDirection(mTextDir)
   9144                     .setLineSpacing(mSpacingAdd, mSpacingMult)
   9145                     .setIncludePad(mIncludePad)
   9146                     .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
   9147                     .setBreakStrategy(mBreakStrategy)
   9148                     .setHyphenationFrequency(mHyphenationFrequency)
   9149                     .setJustificationMode(mJustificationMode)
   9150                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
   9151             if (shouldEllipsize) {
   9152                 builder.setEllipsize(effectiveEllipsize)
   9153                         .setEllipsizedWidth(ellipsisWidth);
   9154             }
   9155             result = builder.build();
   9156         }
   9157         return result;
   9158     }
   9159 
   9160     @UnsupportedAppUsage
   9161     private boolean compressText(float width) {
   9162         if (isHardwareAccelerated()) return false;
   9163 
   9164         // Only compress the text if it hasn't been compressed by the previous pass
   9165         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
   9166                 && mTextPaint.getTextScaleX() == 1.0f) {
   9167             final float textWidth = mLayout.getLineWidth(0);
   9168             final float overflow = (textWidth + 1.0f - width) / width;
   9169             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
   9170                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
   9171                 post(new Runnable() {
   9172                     public void run() {
   9173                         requestLayout();
   9174                     }
   9175                 });
   9176                 return true;
   9177             }
   9178         }
   9179 
   9180         return false;
   9181     }
   9182 
   9183     private static int desired(Layout layout) {
   9184         int n = layout.getLineCount();
   9185         CharSequence text = layout.getText();
   9186         float max = 0;
   9187 
   9188         // if any line was wrapped, we can't use it.
   9189         // but it's ok for the last line not to have a newline
   9190 
   9191         for (int i = 0; i < n - 1; i++) {
   9192             if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
   9193                 return -1;
   9194             }
   9195         }
   9196 
   9197         for (int i = 0; i < n; i++) {
   9198             max = Math.max(max, layout.getLineWidth(i));
   9199         }
   9200 
   9201         return (int) Math.ceil(max);
   9202     }
   9203 
   9204     /**
   9205      * Set whether the TextView includes extra top and bottom padding to make
   9206      * room for accents that go above the normal ascent and descent.
   9207      * The default is true.
   9208      *
   9209      * @see #getIncludeFontPadding()
   9210      *
   9211      * @attr ref android.R.styleable#TextView_includeFontPadding
   9212      */
   9213     public void setIncludeFontPadding(boolean includepad) {
   9214         if (mIncludePad != includepad) {
   9215             mIncludePad = includepad;
   9216 
   9217             if (mLayout != null) {
   9218                 nullLayouts();
   9219                 requestLayout();
   9220                 invalidate();
   9221             }
   9222         }
   9223     }
   9224 
   9225     /**
   9226      * Gets whether the TextView includes extra top and bottom padding to make
   9227      * room for accents that go above the normal ascent and descent.
   9228      *
   9229      * @see #setIncludeFontPadding(boolean)
   9230      *
   9231      * @attr ref android.R.styleable#TextView_includeFontPadding
   9232      */
   9233     @InspectableProperty
   9234     public boolean getIncludeFontPadding() {
   9235         return mIncludePad;
   9236     }
   9237 
   9238     /** @hide */
   9239     @VisibleForTesting
   9240     public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
   9241 
   9242     @Override
   9243     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   9244         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
   9245         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
   9246         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
   9247         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
   9248 
   9249         int width;
   9250         int height;
   9251 
   9252         BoringLayout.Metrics boring = UNKNOWN_BORING;
   9253         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
   9254 
   9255         if (mTextDir == null) {
   9256             mTextDir = getTextDirectionHeuristic();
   9257         }
   9258 
   9259         int des = -1;
   9260         boolean fromexisting = false;
   9261         final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
   9262                 ?  (float) widthSize : Float.MAX_VALUE;
   9263 
   9264         if (widthMode == MeasureSpec.EXACTLY) {
   9265             // Parent has told us how big to be. So be it.
   9266             width = widthSize;
   9267         } else {
   9268             if (mLayout != null && mEllipsize == null) {
   9269                 des = desired(mLayout);
   9270             }
   9271 
   9272             if (des < 0) {
   9273                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
   9274                 if (boring != null) {
   9275                     mBoring = boring;
   9276                 }
   9277             } else {
   9278                 fromexisting = true;
   9279             }
   9280 
   9281             if (boring == null || boring == UNKNOWN_BORING) {
   9282                 if (des < 0) {
   9283                     des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
   9284                             mTransformed.length(), mTextPaint, mTextDir, widthLimit));
   9285                 }
   9286                 width = des;
   9287             } else {
   9288                 width = boring.width;
   9289             }
   9290 
   9291             final Drawables dr = mDrawables;
   9292             if (dr != null) {
   9293                 width = Math.max(width, dr.mDrawableWidthTop);
   9294                 width = Math.max(width, dr.mDrawableWidthBottom);
   9295             }
   9296 
   9297             if (mHint != null) {
   9298                 int hintDes = -1;
   9299                 int hintWidth;
   9300 
   9301                 if (mHintLayout != null && mEllipsize == null) {
   9302                     hintDes = desired(mHintLayout);
   9303                 }
   9304 
   9305                 if (hintDes < 0) {
   9306                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
   9307                     if (hintBoring != null) {
   9308                         mHintBoring = hintBoring;
   9309                     }
   9310                 }
   9311 
   9312                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
   9313                     if (hintDes < 0) {
   9314                         hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
   9315                                 mHint.length(), mTextPaint, mTextDir, widthLimit));
   9316                     }
   9317                     hintWidth = hintDes;
   9318                 } else {
   9319                     hintWidth = hintBoring.width;
   9320                 }
   9321 
   9322                 if (hintWidth > width) {
   9323                     width = hintWidth;
   9324                 }
   9325             }
   9326 
   9327             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
   9328 
   9329             if (mMaxWidthMode == EMS) {
   9330                 width = Math.min(width, mMaxWidth * getLineHeight());
   9331             } else {
   9332                 width = Math.min(width, mMaxWidth);
   9333             }
   9334 
   9335             if (mMinWidthMode == EMS) {
   9336                 width = Math.max(width, mMinWidth * getLineHeight());
   9337             } else {
   9338                 width = Math.max(width, mMinWidth);
   9339             }
   9340 
   9341             // Check against our minimum width
   9342             width = Math.max(width, getSuggestedMinimumWidth());
   9343 
   9344             if (widthMode == MeasureSpec.AT_MOST) {
   9345                 width = Math.min(widthSize, width);
   9346             }
   9347         }
   9348 
   9349         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
   9350         int unpaddedWidth = want;
   9351 
   9352         if (mHorizontallyScrolling) want = VERY_WIDE;
   9353 
   9354         int hintWant = want;
   9355         int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
   9356 
   9357         if (mLayout == null) {
   9358             makeNewLayout(want, hintWant, boring, hintBoring,
   9359                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   9360         } else {
   9361             final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
   9362                     || (mLayout.getEllipsizedWidth()
   9363                             != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
   9364 
   9365             final boolean widthChanged = (mHint == null) && (mEllipsize == null)
   9366                     && (want > mLayout.getWidth())
   9367                     && (mLayout instanceof BoringLayout
   9368                             || (fromexisting && des >= 0 && des <= want));
   9369 
   9370             final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
   9371 
   9372             if (layoutChanged || maximumChanged) {
   9373                 if (!maximumChanged && widthChanged) {
   9374                     mLayout.increaseWidthTo(want);
   9375                 } else {
   9376                     makeNewLayout(want, hintWant, boring, hintBoring,
   9377                             width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
   9378                 }
   9379             } else {
   9380                 // Nothing has changed
   9381             }
   9382         }
   9383 
   9384         if (heightMode == MeasureSpec.EXACTLY) {
   9385             // Parent has told us how big to be. So be it.
   9386             height = heightSize;
   9387             mDesiredHeightAtMeasure = -1;
   9388         } else {
   9389             int desired = getDesiredHeight();
   9390 
   9391             height = desired;
   9392             mDesiredHeightAtMeasure = desired;
   9393 
   9394             if (heightMode == MeasureSpec.AT_MOST) {
   9395                 height = Math.min(desired, heightSize);
   9396             }
   9397         }
   9398 
   9399         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
   9400         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
   9401             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
   9402         }
   9403 
   9404         /*
   9405          * We didn't let makeNewLayout() register to bring the cursor into view,
   9406          * so do it here if there is any possibility that it is needed.
   9407          */
   9408         if (mMovement != null
   9409                 || mLayout.getWidth() > unpaddedWidth
   9410                 || mLayout.getHeight() > unpaddedHeight) {
   9411             registerForPreDraw();
   9412         } else {
   9413             scrollTo(0, 0);
   9414         }
   9415 
   9416         setMeasuredDimension(width, height);
   9417     }
   9418 
   9419     /**
   9420      * Automatically computes and sets the text size.
   9421      */
   9422     private void autoSizeText() {
   9423         if (!isAutoSizeEnabled()) {
   9424             return;
   9425         }
   9426 
   9427         if (mNeedsAutoSizeText) {
   9428             if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
   9429                 return;
   9430             }
   9431 
   9432             final int availableWidth = mHorizontallyScrolling
   9433                     ? VERY_WIDE
   9434                     : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
   9435             final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
   9436                     - getExtendedPaddingTop();
   9437 
   9438             if (availableWidth <= 0 || availableHeight <= 0) {
   9439                 return;
   9440             }
   9441 
   9442             synchronized (TEMP_RECTF) {
   9443                 TEMP_RECTF.setEmpty();
   9444                 TEMP_RECTF.right = availableWidth;
   9445                 TEMP_RECTF.bottom = availableHeight;
   9446                 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
   9447 
   9448                 if (optimalTextSize != getTextSize()) {
   9449                     setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
   9450                             false /* shouldRequestLayout */);
   9451 
   9452                     makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
   9453                             mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
   9454                             false /* bringIntoView */);
   9455                 }
   9456             }
   9457         }
   9458         // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
   9459         // after the next layout pass should set this to false.
   9460         mNeedsAutoSizeText = true;
   9461     }
   9462 
   9463     /**
   9464      * Performs a binary search to find the largest text size that will still fit within the size
   9465      * available to this view.
   9466      */
   9467     private int findLargestTextSizeWhichFits(RectF availableSpace) {
   9468         final int sizesCount = mAutoSizeTextSizesInPx.length;
   9469         if (sizesCount == 0) {
   9470             throw new IllegalStateException("No available text sizes to choose from.");
   9471         }
   9472 
   9473         int bestSizeIndex = 0;
   9474         int lowIndex = bestSizeIndex + 1;
   9475         int highIndex = sizesCount - 1;
   9476         int sizeToTryIndex;
   9477         while (lowIndex <= highIndex) {
   9478             sizeToTryIndex = (lowIndex + highIndex) / 2;
   9479             if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
   9480                 bestSizeIndex = lowIndex;
   9481                 lowIndex = sizeToTryIndex + 1;
   9482             } else {
   9483                 highIndex = sizeToTryIndex - 1;
   9484                 bestSizeIndex = highIndex;
   9485             }
   9486         }
   9487 
   9488         return mAutoSizeTextSizesInPx[bestSizeIndex];
   9489     }
   9490 
   9491     private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
   9492         final CharSequence text = mTransformed != null
   9493                 ? mTransformed
   9494                 : getText();
   9495         final int maxLines = getMaxLines();
   9496         if (mTempTextPaint == null) {
   9497             mTempTextPaint = new TextPaint();
   9498         } else {
   9499             mTempTextPaint.reset();
   9500         }
   9501         mTempTextPaint.set(getPaint());
   9502         mTempTextPaint.setTextSize(suggestedSizeInPx);
   9503 
   9504         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
   9505                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
   9506 
   9507         layoutBuilder.setAlignment(getLayoutAlignment())
   9508                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
   9509                 .setIncludePad(getIncludeFontPadding())
   9510                 .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
   9511                 .setBreakStrategy(getBreakStrategy())
   9512                 .setHyphenationFrequency(getHyphenationFrequency())
   9513                 .setJustificationMode(getJustificationMode())
   9514                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
   9515                 .setTextDirection(getTextDirectionHeuristic());
   9516 
   9517         final StaticLayout layout = layoutBuilder.build();
   9518 
   9519         // Lines overflow.
   9520         if (maxLines != -1 && layout.getLineCount() > maxLines) {
   9521             return false;
   9522         }
   9523 
   9524         // Height overflow.
   9525         if (layout.getHeight() > availableSpace.bottom) {
   9526             return false;
   9527         }
   9528 
   9529         return true;
   9530     }
   9531 
   9532     private int getDesiredHeight() {
   9533         return Math.max(
   9534                 getDesiredHeight(mLayout, true),
   9535                 getDesiredHeight(mHintLayout, mEllipsize != null));
   9536     }
   9537 
   9538     private int getDesiredHeight(Layout layout, boolean cap) {
   9539         if (layout == null) {
   9540             return 0;
   9541         }
   9542 
   9543         /*
   9544         * Don't cap the hint to a certain number of lines.
   9545         * (Do cap it, though, if we have a maximum pixel height.)
   9546         */
   9547         int desired = layout.getHeight(cap);
   9548 
   9549         final Drawables dr = mDrawables;
   9550         if (dr != null) {
   9551             desired = Math.max(desired, dr.mDrawableHeightLeft);
   9552             desired = Math.max(desired, dr.mDrawableHeightRight);
   9553         }
   9554 
   9555         int linecount = layout.getLineCount();
   9556         final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
   9557         desired += padding;
   9558 
   9559         if (mMaxMode != LINES) {
   9560             desired = Math.min(desired, mMaximum);
   9561         } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
   9562                 || layout instanceof BoringLayout)) {
   9563             desired = layout.getLineTop(mMaximum);
   9564 
   9565             if (dr != null) {
   9566                 desired = Math.max(desired, dr.mDrawableHeightLeft);
   9567                 desired = Math.max(desired, dr.mDrawableHeightRight);
   9568             }
   9569 
   9570             desired += padding;
   9571             linecount = mMaximum;
   9572         }
   9573 
   9574         if (mMinMode == LINES) {
   9575             if (linecount < mMinimum) {
   9576                 desired += getLineHeight() * (mMinimum - linecount);
   9577             }
   9578         } else {
   9579             desired = Math.max(desired, mMinimum);
   9580         }
   9581 
   9582         // Check against our minimum height
   9583         desired = Math.max(desired, getSuggestedMinimumHeight());
   9584 
   9585         return desired;
   9586     }
   9587 
   9588     /**
   9589      * Check whether a change to the existing text layout requires a
   9590      * new view layout.
   9591      */
   9592     private void checkForResize() {
   9593         boolean sizeChanged = false;
   9594 
   9595         if (mLayout != null) {
   9596             // Check if our width changed
   9597             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
   9598                 sizeChanged = true;
   9599                 invalidate();
   9600             }
   9601 
   9602             // Check if our height changed
   9603             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
   9604                 int desiredHeight = getDesiredHeight();
   9605 
   9606                 if (desiredHeight != this.getHeight()) {
   9607                     sizeChanged = true;
   9608                 }
   9609             } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
   9610                 if (mDesiredHeightAtMeasure >= 0) {
   9611                     int desiredHeight = getDesiredHeight();
   9612 
   9613                     if (desiredHeight != mDesiredHeightAtMeasure) {
   9614                         sizeChanged = true;
   9615                     }
   9616                 }
   9617             }
   9618         }
   9619 
   9620         if (sizeChanged) {
   9621             requestLayout();
   9622             // caller will have already invalidated
   9623         }
   9624     }
   9625 
   9626     /**
   9627      * Check whether entirely new text requires a new view layout
   9628      * or merely a new text layout.
   9629      */
   9630     @UnsupportedAppUsage
   9631     private void checkForRelayout() {
   9632         // If we have a fixed width, we can just swap in a new text layout
   9633         // if the text height stays the same or if the view height is fixed.
   9634 
   9635         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
   9636                 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
   9637                 && (mHint == null || mHintLayout != null)
   9638                 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
   9639             // Static width, so try making a new text layout.
   9640 
   9641             int oldht = mLayout.getHeight();
   9642             int want = mLayout.getWidth();
   9643             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
   9644 
   9645             /*
   9646              * No need to bring the text into view, since the size is not
   9647              * changing (unless we do the requestLayout(), in which case it
   9648              * will happen at measure).
   9649              */
   9650             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
   9651                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
   9652                           false);
   9653 
   9654             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
   9655                 // In a fixed-height view, so use our new text layout.
   9656                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
   9657                         && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
   9658                     autoSizeText();
   9659                     invalidate();
   9660                     return;
   9661                 }
   9662 
   9663                 // Dynamic height, but height has stayed the same,
   9664                 // so use our new text layout.
   9665                 if (mLayout.getHeight() == oldht
   9666                         && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
   9667                     autoSizeText();
   9668                     invalidate();
   9669                     return;
   9670                 }
   9671             }
   9672 
   9673             // We lose: the height has changed and we have a dynamic height.
   9674             // Request a new view layout using our new text layout.
   9675             requestLayout();
   9676             invalidate();
   9677         } else {
   9678             // Dynamic width, so we have no choice but to request a new
   9679             // view layout with a new text layout.
   9680             nullLayouts();
   9681             requestLayout();
   9682             invalidate();
   9683         }
   9684     }
   9685 
   9686     @Override
   9687     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   9688         super.onLayout(changed, left, top, right, bottom);
   9689         if (mDeferScroll >= 0) {
   9690             int curs = mDeferScroll;
   9691             mDeferScroll = -1;
   9692             bringPointIntoView(Math.min(curs, mText.length()));
   9693         }
   9694         // Call auto-size after the width and height have been calculated.
   9695         autoSizeText();
   9696     }
   9697 
   9698     private boolean isShowingHint() {
   9699         return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
   9700     }
   9701 
   9702     /**
   9703      * Returns true if anything changed.
   9704      */
   9705     @UnsupportedAppUsage
   9706     private boolean bringTextIntoView() {
   9707         Layout layout = isShowingHint() ? mHintLayout : mLayout;
   9708         int line = 0;
   9709         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   9710             line = layout.getLineCount() - 1;
   9711         }
   9712 
   9713         Layout.Alignment a = layout.getParagraphAlignment(line);
   9714         int dir = layout.getParagraphDirection(line);
   9715         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   9716         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   9717         int ht = layout.getHeight();
   9718 
   9719         int scrollx, scrolly;
   9720 
   9721         // Convert to left, center, or right alignment.
   9722         if (a == Layout.Alignment.ALIGN_NORMAL) {
   9723             a = dir == Layout.DIR_LEFT_TO_RIGHT
   9724                     ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
   9725         } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
   9726             a = dir == Layout.DIR_LEFT_TO_RIGHT
   9727                     ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
   9728         }
   9729 
   9730         if (a == Layout.Alignment.ALIGN_CENTER) {
   9731             /*
   9732              * Keep centered if possible, or, if it is too wide to fit,
   9733              * keep leading edge in view.
   9734              */
   9735 
   9736             int left = (int) Math.floor(layout.getLineLeft(line));
   9737             int right = (int) Math.ceil(layout.getLineRight(line));
   9738 
   9739             if (right - left < hspace) {
   9740                 scrollx = (right + left) / 2 - hspace / 2;
   9741             } else {
   9742                 if (dir < 0) {
   9743                     scrollx = right - hspace;
   9744                 } else {
   9745                     scrollx = left;
   9746                 }
   9747             }
   9748         } else if (a == Layout.Alignment.ALIGN_RIGHT) {
   9749             int right = (int) Math.ceil(layout.getLineRight(line));
   9750             scrollx = right - hspace;
   9751         } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
   9752             scrollx = (int) Math.floor(layout.getLineLeft(line));
   9753         }
   9754 
   9755         if (ht < vspace) {
   9756             scrolly = 0;
   9757         } else {
   9758             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   9759                 scrolly = ht - vspace;
   9760             } else {
   9761                 scrolly = 0;
   9762             }
   9763         }
   9764 
   9765         if (scrollx != mScrollX || scrolly != mScrollY) {
   9766             scrollTo(scrollx, scrolly);
   9767             return true;
   9768         } else {
   9769             return false;
   9770         }
   9771     }
   9772 
   9773     /**
   9774      * Move the point, specified by the offset, into the view if it is needed.
   9775      * This has to be called after layout. Returns true if anything changed.
   9776      */
   9777     public boolean bringPointIntoView(int offset) {
   9778         if (isLayoutRequested()) {
   9779             mDeferScroll = offset;
   9780             return false;
   9781         }
   9782         boolean changed = false;
   9783 
   9784         Layout layout = isShowingHint() ? mHintLayout : mLayout;
   9785 
   9786         if (layout == null) return changed;
   9787 
   9788         int line = layout.getLineForOffset(offset);
   9789 
   9790         int grav;
   9791 
   9792         switch (layout.getParagraphAlignment(line)) {
   9793             case ALIGN_LEFT:
   9794                 grav = 1;
   9795                 break;
   9796             case ALIGN_RIGHT:
   9797                 grav = -1;
   9798                 break;
   9799             case ALIGN_NORMAL:
   9800                 grav = layout.getParagraphDirection(line);
   9801                 break;
   9802             case ALIGN_OPPOSITE:
   9803                 grav = -layout.getParagraphDirection(line);
   9804                 break;
   9805             case ALIGN_CENTER:
   9806             default:
   9807                 grav = 0;
   9808                 break;
   9809         }
   9810 
   9811         // We only want to clamp the cursor to fit within the layout width
   9812         // in left-to-right modes, because in a right to left alignment,
   9813         // we want to scroll to keep the line-right on the screen, as other
   9814         // lines are likely to have text flush with the right margin, which
   9815         // we want to keep visible.
   9816         // A better long-term solution would probably be to measure both
   9817         // the full line and a blank-trimmed version, and, for example, use
   9818         // the latter measurement for centering and right alignment, but for
   9819         // the time being we only implement the cursor clamping in left to
   9820         // right where it is most likely to be annoying.
   9821         final boolean clamped = grav > 0;
   9822         // FIXME: Is it okay to truncate this, or should we round?
   9823         final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
   9824         final int top = layout.getLineTop(line);
   9825         final int bottom = layout.getLineTop(line + 1);
   9826 
   9827         int left = (int) Math.floor(layout.getLineLeft(line));
   9828         int right = (int) Math.ceil(layout.getLineRight(line));
   9829         int ht = layout.getHeight();
   9830 
   9831         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   9832         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   9833         if (!mHorizontallyScrolling && right - left > hspace && right > x) {
   9834             // If cursor has been clamped, make sure we don't scroll.
   9835             right = Math.max(x, left + hspace);
   9836         }
   9837 
   9838         int hslack = (bottom - top) / 2;
   9839         int vslack = hslack;
   9840 
   9841         if (vslack > vspace / 4) {
   9842             vslack = vspace / 4;
   9843         }
   9844         if (hslack > hspace / 4) {
   9845             hslack = hspace / 4;
   9846         }
   9847 
   9848         int hs = mScrollX;
   9849         int vs = mScrollY;
   9850 
   9851         if (top - vs < vslack) {
   9852             vs = top - vslack;
   9853         }
   9854         if (bottom - vs > vspace - vslack) {
   9855             vs = bottom - (vspace - vslack);
   9856         }
   9857         if (ht - vs < vspace) {
   9858             vs = ht - vspace;
   9859         }
   9860         if (0 - vs > 0) {
   9861             vs = 0;
   9862         }
   9863 
   9864         if (grav != 0) {
   9865             if (x - hs < hslack) {
   9866                 hs = x - hslack;
   9867             }
   9868             if (x - hs > hspace - hslack) {
   9869                 hs = x - (hspace - hslack);
   9870             }
   9871         }
   9872 
   9873         if (grav < 0) {
   9874             if (left - hs > 0) {
   9875                 hs = left;
   9876             }
   9877             if (right - hs < hspace) {
   9878                 hs = right - hspace;
   9879             }
   9880         } else if (grav > 0) {
   9881             if (right - hs < hspace) {
   9882                 hs = right - hspace;
   9883             }
   9884             if (left - hs > 0) {
   9885                 hs = left;
   9886             }
   9887         } else /* grav == 0 */ {
   9888             if (right - left <= hspace) {
   9889                 /*
   9890                  * If the entire text fits, center it exactly.
   9891                  */
   9892                 hs = left - (hspace - (right - left)) / 2;
   9893             } else if (x > right - hslack) {
   9894                 /*
   9895                  * If we are near the right edge, keep the right edge
   9896                  * at the edge of the view.
   9897                  */
   9898                 hs = right - hspace;
   9899             } else if (x < left + hslack) {
   9900                 /*
   9901                  * If we are near the left edge, keep the left edge
   9902                  * at the edge of the view.
   9903                  */
   9904                 hs = left;
   9905             } else if (left > hs) {
   9906                 /*
   9907                  * Is there whitespace visible at the left?  Fix it if so.
   9908                  */
   9909                 hs = left;
   9910             } else if (right < hs + hspace) {
   9911                 /*
   9912                  * Is there whitespace visible at the right?  Fix it if so.
   9913                  */
   9914                 hs = right - hspace;
   9915             } else {
   9916                 /*
   9917                  * Otherwise, float as needed.
   9918                  */
   9919                 if (x - hs < hslack) {
   9920                     hs = x - hslack;
   9921                 }
   9922                 if (x - hs > hspace - hslack) {
   9923                     hs = x - (hspace - hslack);
   9924                 }
   9925             }
   9926         }
   9927 
   9928         if (hs != mScrollX || vs != mScrollY) {
   9929             if (mScroller == null) {
   9930                 scrollTo(hs, vs);
   9931             } else {
   9932                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
   9933                 int dx = hs - mScrollX;
   9934                 int dy = vs - mScrollY;
   9935 
   9936                 if (duration > ANIMATED_SCROLL_GAP) {
   9937                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
   9938                     awakenScrollBars(mScroller.getDuration());
   9939                     invalidate();
   9940                 } else {
   9941                     if (!mScroller.isFinished()) {
   9942                         mScroller.abortAnimation();
   9943                     }
   9944 
   9945                     scrollBy(dx, dy);
   9946                 }
   9947 
   9948                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
   9949             }
   9950 
   9951             changed = true;
   9952         }
   9953 
   9954         if (isFocused()) {
   9955             // This offsets because getInterestingRect() is in terms of viewport coordinates, but
   9956             // requestRectangleOnScreen() is in terms of content coordinates.
   9957 
   9958             // The offsets here are to ensure the rectangle we are using is
   9959             // within our view bounds, in case the cursor is on the far left
   9960             // or right.  If it isn't withing the bounds, then this request
   9961             // will be ignored.
   9962             if (mTempRect == null) mTempRect = new Rect();
   9963             mTempRect.set(x - 2, top, x + 2, bottom);
   9964             getInterestingRect(mTempRect, line);
   9965             mTempRect.offset(mScrollX, mScrollY);
   9966 
   9967             if (requestRectangleOnScreen(mTempRect)) {
   9968                 changed = true;
   9969             }
   9970         }
   9971 
   9972         return changed;
   9973     }
   9974 
   9975     /**
   9976      * Move the cursor, if needed, so that it is at an offset that is visible
   9977      * to the user.  This will not move the cursor if it represents more than
   9978      * one character (a selection range).  This will only work if the
   9979      * TextView contains spannable text; otherwise it will do nothing.
   9980      *
   9981      * @return True if the cursor was actually moved, false otherwise.
   9982      */
   9983     public boolean moveCursorToVisibleOffset() {
   9984         if (!(mText instanceof Spannable)) {
   9985             return false;
   9986         }
   9987         int start = getSelectionStart();
   9988         int end = getSelectionEnd();
   9989         if (start != end) {
   9990             return false;
   9991         }
   9992 
   9993         // First: make sure the line is visible on screen:
   9994 
   9995         int line = mLayout.getLineForOffset(start);
   9996 
   9997         final int top = mLayout.getLineTop(line);
   9998         final int bottom = mLayout.getLineTop(line + 1);
   9999         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
   10000         int vslack = (bottom - top) / 2;
   10001         if (vslack > vspace / 4) {
   10002             vslack = vspace / 4;
   10003         }
   10004         final int vs = mScrollY;
   10005 
   10006         if (top < (vs + vslack)) {
   10007             line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
   10008         } else if (bottom > (vspace + vs - vslack)) {
   10009             line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
   10010         }
   10011 
   10012         // Next: make sure the character is visible on screen:
   10013 
   10014         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   10015         final int hs = mScrollX;
   10016         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
   10017         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
   10018 
   10019         // line might contain bidirectional text
   10020         final int lowChar = leftChar < rightChar ? leftChar : rightChar;
   10021         final int highChar = leftChar > rightChar ? leftChar : rightChar;
   10022 
   10023         int newStart = start;
   10024         if (newStart < lowChar) {
   10025             newStart = lowChar;
   10026         } else if (newStart > highChar) {
   10027             newStart = highChar;
   10028         }
   10029 
   10030         if (newStart != start) {
   10031             Selection.setSelection(mSpannable, newStart);
   10032             return true;
   10033         }
   10034 
   10035         return false;
   10036     }
   10037 
   10038     @Override
   10039     public void computeScroll() {
   10040         if (mScroller != null) {
   10041             if (mScroller.computeScrollOffset()) {
   10042                 mScrollX = mScroller.getCurrX();
   10043                 mScrollY = mScroller.getCurrY();
   10044                 invalidateParentCaches();
   10045                 postInvalidate();  // So we draw again
   10046             }
   10047         }
   10048     }
   10049 
   10050     private void getInterestingRect(Rect r, int line) {
   10051         convertFromViewportToContentCoordinates(r);
   10052 
   10053         // Rectangle can can be expanded on first and last line to take
   10054         // padding into account.
   10055         // TODO Take left/right padding into account too?
   10056         if (line == 0) r.top -= getExtendedPaddingTop();
   10057         if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
   10058     }
   10059 
   10060     private void convertFromViewportToContentCoordinates(Rect r) {
   10061         final int horizontalOffset = viewportToContentHorizontalOffset();
   10062         r.left += horizontalOffset;
   10063         r.right += horizontalOffset;
   10064 
   10065         final int verticalOffset = viewportToContentVerticalOffset();
   10066         r.top += verticalOffset;
   10067         r.bottom += verticalOffset;
   10068     }
   10069 
   10070     int viewportToContentHorizontalOffset() {
   10071         return getCompoundPaddingLeft() - mScrollX;
   10072     }
   10073 
   10074     @UnsupportedAppUsage
   10075     int viewportToContentVerticalOffset() {
   10076         int offset = getExtendedPaddingTop() - mScrollY;
   10077         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
   10078             offset += getVerticalOffset(false);
   10079         }
   10080         return offset;
   10081     }
   10082 
   10083     @Override
   10084     public void debug(int depth) {
   10085         super.debug(depth);
   10086 
   10087         String output = debugIndent(depth);
   10088         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
   10089                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
   10090                 + "} ";
   10091 
   10092         if (mText != null) {
   10093 
   10094             output += "mText=\"" + mText + "\" ";
   10095             if (mLayout != null) {
   10096                 output += "mLayout width=" + mLayout.getWidth()
   10097                         + " height=" + mLayout.getHeight();
   10098             }
   10099         } else {
   10100             output += "mText=NULL";
   10101         }
   10102         Log.d(VIEW_LOG_TAG, output);
   10103     }
   10104 
   10105     /**
   10106      * Convenience for {@link Selection#getSelectionStart}.
   10107      */
   10108     @ViewDebug.ExportedProperty(category = "text")
   10109     public int getSelectionStart() {
   10110         return Selection.getSelectionStart(getText());
   10111     }
   10112 
   10113     /**
   10114      * Convenience for {@link Selection#getSelectionEnd}.
   10115      */
   10116     @ViewDebug.ExportedProperty(category = "text")
   10117     public int getSelectionEnd() {
   10118         return Selection.getSelectionEnd(getText());
   10119     }
   10120 
   10121     /**
   10122      * Return true iff there is a selection of nonzero length inside this text view.
   10123      */
   10124     public boolean hasSelection() {
   10125         final int selectionStart = getSelectionStart();
   10126         final int selectionEnd = getSelectionEnd();
   10127 
   10128         return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd;
   10129     }
   10130 
   10131     String getSelectedText() {
   10132         if (!hasSelection()) {
   10133             return null;
   10134         }
   10135 
   10136         final int start = getSelectionStart();
   10137         final int end = getSelectionEnd();
   10138         return String.valueOf(
   10139                 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
   10140     }
   10141 
   10142     /**
   10143      * Sets the properties of this field (lines, horizontally scrolling,
   10144      * transformation method) to be for a single-line input.
   10145      *
   10146      * @attr ref android.R.styleable#TextView_singleLine
   10147      */
   10148     public void setSingleLine() {
   10149         setSingleLine(true);
   10150     }
   10151 
   10152     /**
   10153      * Sets the properties of this field to transform input to ALL CAPS
   10154      * display. This may use a "small caps" formatting if available.
   10155      * This setting will be ignored if this field is editable or selectable.
   10156      *
   10157      * This call replaces the current transformation method. Disabling this
   10158      * will not necessarily restore the previous behavior from before this
   10159      * was enabled.
   10160      *
   10161      * @see #setTransformationMethod(TransformationMethod)
   10162      * @attr ref android.R.styleable#TextView_textAllCaps
   10163      */
   10164     public void setAllCaps(boolean allCaps) {
   10165         if (allCaps) {
   10166             setTransformationMethod(new AllCapsTransformationMethod(getContext()));
   10167         } else {
   10168             setTransformationMethod(null);
   10169         }
   10170     }
   10171 
   10172     /**
   10173      *
   10174      * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
   10175      * @return Whether the current transformation method is for ALL CAPS.
   10176      *
   10177      * @see #setAllCaps(boolean)
   10178      * @see #setTransformationMethod(TransformationMethod)
   10179      */
   10180     @InspectableProperty(name = "textAllCaps")
   10181     public boolean isAllCaps() {
   10182         final TransformationMethod method = getTransformationMethod();
   10183         return method != null && method instanceof AllCapsTransformationMethod;
   10184     }
   10185 
   10186     /**
   10187      * If true, sets the properties of this field (number of lines, horizontally scrolling,
   10188      * transformation method) to be for a single-line input; if false, restores these to the default
   10189      * conditions.
   10190      *
   10191      * Note that the default conditions are not necessarily those that were in effect prior this
   10192      * method, and you may want to reset these properties to your custom values.
   10193      *
   10194      * @attr ref android.R.styleable#TextView_singleLine
   10195      */
   10196     @android.view.RemotableViewMethod
   10197     public void setSingleLine(boolean singleLine) {
   10198         // Could be used, but may break backward compatibility.
   10199         // if (mSingleLine == singleLine) return;
   10200         setInputTypeSingleLine(singleLine);
   10201         applySingleLine(singleLine, true, true);
   10202     }
   10203 
   10204     /**
   10205      * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
   10206      * @param singleLine
   10207      */
   10208     private void setInputTypeSingleLine(boolean singleLine) {
   10209         if (mEditor != null
   10210                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
   10211                         == EditorInfo.TYPE_CLASS_TEXT) {
   10212             if (singleLine) {
   10213                 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   10214             } else {
   10215                 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
   10216             }
   10217         }
   10218     }
   10219 
   10220     private void applySingleLine(boolean singleLine, boolean applyTransformation,
   10221             boolean changeMaxLines) {
   10222         mSingleLine = singleLine;
   10223         if (singleLine) {
   10224             setLines(1);
   10225             setHorizontallyScrolling(true);
   10226             if (applyTransformation) {
   10227                 setTransformationMethod(SingleLineTransformationMethod.getInstance());
   10228             }
   10229         } else {
   10230             if (changeMaxLines) {
   10231                 setMaxLines(Integer.MAX_VALUE);
   10232             }
   10233             setHorizontallyScrolling(false);
   10234             if (applyTransformation) {
   10235                 setTransformationMethod(null);
   10236             }
   10237         }
   10238     }
   10239 
   10240     /**
   10241      * Causes words in the text that are longer than the view's width
   10242      * to be ellipsized instead of broken in the middle.  You may also
   10243      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
   10244      * to constrain the text to a single line.  Use <code>null</code>
   10245      * to turn off ellipsizing.
   10246      *
   10247      * If {@link #setMaxLines} has been used to set two or more lines,
   10248      * only {@link android.text.TextUtils.TruncateAt#END} and
   10249      * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
   10250      * (other ellipsizing types will not do anything).
   10251      *
   10252      * @attr ref android.R.styleable#TextView_ellipsize
   10253      */
   10254     public void setEllipsize(TextUtils.TruncateAt where) {
   10255         // TruncateAt is an enum. != comparison is ok between these singleton objects.
   10256         if (mEllipsize != where) {
   10257             mEllipsize = where;
   10258 
   10259             if (mLayout != null) {
   10260                 nullLayouts();
   10261                 requestLayout();
   10262                 invalidate();
   10263             }
   10264         }
   10265     }
   10266 
   10267     /**
   10268      * Sets how many times to repeat the marquee animation. Only applied if the
   10269      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
   10270      *
   10271      * @see #getMarqueeRepeatLimit()
   10272      *
   10273      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
   10274      */
   10275     public void setMarqueeRepeatLimit(int marqueeLimit) {
   10276         mMarqueeRepeatLimit = marqueeLimit;
   10277     }
   10278 
   10279     /**
   10280      * Gets the number of times the marquee animation is repeated. Only meaningful if the
   10281      * TextView has marquee enabled.
   10282      *
   10283      * @return the number of times the marquee animation is repeated. -1 if the animation
   10284      * repeats indefinitely
   10285      *
   10286      * @see #setMarqueeRepeatLimit(int)
   10287      *
   10288      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
   10289      */
   10290     @InspectableProperty
   10291     public int getMarqueeRepeatLimit() {
   10292         return mMarqueeRepeatLimit;
   10293     }
   10294 
   10295     /**
   10296      * Returns where, if anywhere, words that are longer than the view
   10297      * is wide should be ellipsized.
   10298      */
   10299     @InspectableProperty
   10300     @ViewDebug.ExportedProperty
   10301     public TextUtils.TruncateAt getEllipsize() {
   10302         return mEllipsize;
   10303     }
   10304 
   10305     /**
   10306      * Set the TextView so that when it takes focus, all the text is
   10307      * selected.
   10308      *
   10309      * @attr ref android.R.styleable#TextView_selectAllOnFocus
   10310      */
   10311     @android.view.RemotableViewMethod
   10312     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
   10313         createEditorIfNeeded();
   10314         mEditor.mSelectAllOnFocus = selectAllOnFocus;
   10315 
   10316         if (selectAllOnFocus && !(mText instanceof Spannable)) {
   10317             setText(mText, BufferType.SPANNABLE);
   10318         }
   10319     }
   10320 
   10321     /**
   10322      * Set whether the cursor is visible. The default is true. Note that this property only
   10323      * makes sense for editable TextView.
   10324      *
   10325      * @see #isCursorVisible()
   10326      *
   10327      * @attr ref android.R.styleable#TextView_cursorVisible
   10328      */
   10329     @android.view.RemotableViewMethod
   10330     public void setCursorVisible(boolean visible) {
   10331         if (visible && mEditor == null) return; // visible is the default value with no edit data
   10332         createEditorIfNeeded();
   10333         if (mEditor.mCursorVisible != visible) {
   10334             mEditor.mCursorVisible = visible;
   10335             invalidate();
   10336 
   10337             mEditor.makeBlink();
   10338 
   10339             // InsertionPointCursorController depends on mCursorVisible
   10340             mEditor.prepareCursorControllers();
   10341         }
   10342     }
   10343 
   10344     /**
   10345      * @return whether or not the cursor is visible (assuming this TextView is editable)
   10346      *
   10347      * @see #setCursorVisible(boolean)
   10348      *
   10349      * @attr ref android.R.styleable#TextView_cursorVisible
   10350      */
   10351     @InspectableProperty
   10352     public boolean isCursorVisible() {
   10353         // true is the default value
   10354         return mEditor == null ? true : mEditor.mCursorVisible;
   10355     }
   10356 
   10357     private boolean canMarquee() {
   10358         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
   10359         return width > 0 && (mLayout.getLineWidth(0) > width
   10360                 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
   10361                         && mSavedMarqueeModeLayout.getLineWidth(0) > width));
   10362     }
   10363 
   10364     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
   10365     private void startMarquee() {
   10366         // Do not ellipsize EditText
   10367         if (getKeyListener() != null) return;
   10368 
   10369         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
   10370             return;
   10371         }
   10372 
   10373         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
   10374                 && getLineCount() == 1 && canMarquee()) {
   10375 
   10376             if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
   10377                 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
   10378                 final Layout tmp = mLayout;
   10379                 mLayout = mSavedMarqueeModeLayout;
   10380                 mSavedMarqueeModeLayout = tmp;
   10381                 setHorizontalFadingEdgeEnabled(true);
   10382                 requestLayout();
   10383                 invalidate();
   10384             }
   10385 
   10386             if (mMarquee == null) mMarquee = new Marquee(this);
   10387             mMarquee.start(mMarqueeRepeatLimit);
   10388         }
   10389     }
   10390 
   10391     private void stopMarquee() {
   10392         if (mMarquee != null && !mMarquee.isStopped()) {
   10393             mMarquee.stop();
   10394         }
   10395 
   10396         if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
   10397             mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   10398             final Layout tmp = mSavedMarqueeModeLayout;
   10399             mSavedMarqueeModeLayout = mLayout;
   10400             mLayout = tmp;
   10401             setHorizontalFadingEdgeEnabled(false);
   10402             requestLayout();
   10403             invalidate();
   10404         }
   10405     }
   10406 
   10407     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
   10408     private void startStopMarquee(boolean start) {
   10409         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   10410             if (start) {
   10411                 startMarquee();
   10412             } else {
   10413                 stopMarquee();
   10414             }
   10415         }
   10416     }
   10417 
   10418     /**
   10419      * This method is called when the text is changed, in case any subclasses
   10420      * would like to know.
   10421      *
   10422      * Within <code>text</code>, the <code>lengthAfter</code> characters
   10423      * beginning at <code>start</code> have just replaced old text that had
   10424      * length <code>lengthBefore</code>. It is an error to attempt to make
   10425      * changes to <code>text</code> from this callback.
   10426      *
   10427      * @param text The text the TextView is displaying
   10428      * @param start The offset of the start of the range of the text that was
   10429      * modified
   10430      * @param lengthBefore The length of the former text that has been replaced
   10431      * @param lengthAfter The length of the replacement modified text
   10432      */
   10433     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
   10434         // intentionally empty, template pattern method can be overridden by subclasses
   10435     }
   10436 
   10437     /**
   10438      * This method is called when the selection has changed, in case any
   10439      * subclasses would like to know.
   10440      *
   10441      * @param selStart The new selection start location.
   10442      * @param selEnd The new selection end location.
   10443      */
   10444     protected void onSelectionChanged(int selStart, int selEnd) {
   10445         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
   10446     }
   10447 
   10448     /**
   10449      * Adds a TextWatcher to the list of those whose methods are called
   10450      * whenever this TextView's text changes.
   10451      * <p>
   10452      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
   10453      * not called after {@link #setText} calls.  Now, doing {@link #setText}
   10454      * if there are any text changed listeners forces the buffer type to
   10455      * Editable if it would not otherwise be and does call this method.
   10456      */
   10457     public void addTextChangedListener(TextWatcher watcher) {
   10458         if (mListeners == null) {
   10459             mListeners = new ArrayList<TextWatcher>();
   10460         }
   10461 
   10462         mListeners.add(watcher);
   10463     }
   10464 
   10465     /**
   10466      * Removes the specified TextWatcher from the list of those whose
   10467      * methods are called
   10468      * whenever this TextView's text changes.
   10469      */
   10470     public void removeTextChangedListener(TextWatcher watcher) {
   10471         if (mListeners != null) {
   10472             int i = mListeners.indexOf(watcher);
   10473 
   10474             if (i >= 0) {
   10475                 mListeners.remove(i);
   10476             }
   10477         }
   10478     }
   10479 
   10480     private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
   10481         if (mListeners != null) {
   10482             final ArrayList<TextWatcher> list = mListeners;
   10483             final int count = list.size();
   10484             for (int i = 0; i < count; i++) {
   10485                 list.get(i).beforeTextChanged(text, start, before, after);
   10486             }
   10487         }
   10488 
   10489         // The spans that are inside or intersect the modified region no longer make sense
   10490         removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
   10491         removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
   10492     }
   10493 
   10494     // Removes all spans that are inside or actually overlap the start..end range
   10495     private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
   10496         if (!(mText instanceof Editable)) return;
   10497         Editable text = (Editable) mText;
   10498 
   10499         T[] spans = text.getSpans(start, end, type);
   10500         final int length = spans.length;
   10501         for (int i = 0; i < length; i++) {
   10502             final int spanStart = text.getSpanStart(spans[i]);
   10503             final int spanEnd = text.getSpanEnd(spans[i]);
   10504             if (spanEnd == start || spanStart == end) break;
   10505             text.removeSpan(spans[i]);
   10506         }
   10507     }
   10508 
   10509     void removeAdjacentSuggestionSpans(final int pos) {
   10510         if (!(mText instanceof Editable)) return;
   10511         final Editable text = (Editable) mText;
   10512 
   10513         final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
   10514         final int length = spans.length;
   10515         for (int i = 0; i < length; i++) {
   10516             final int spanStart = text.getSpanStart(spans[i]);
   10517             final int spanEnd = text.getSpanEnd(spans[i]);
   10518             if (spanEnd == pos || spanStart == pos) {
   10519                 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
   10520                     text.removeSpan(spans[i]);
   10521                 }
   10522             }
   10523         }
   10524     }
   10525 
   10526     /**
   10527      * Not private so it can be called from an inner class without going
   10528      * through a thunk.
   10529      */
   10530     void sendOnTextChanged(CharSequence text, int start, int before, int after) {
   10531         if (mListeners != null) {
   10532             final ArrayList<TextWatcher> list = mListeners;
   10533             final int count = list.size();
   10534             for (int i = 0; i < count; i++) {
   10535                 list.get(i).onTextChanged(text, start, before, after);
   10536             }
   10537         }
   10538 
   10539         if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
   10540     }
   10541 
   10542     /**
   10543      * Not private so it can be called from an inner class without going
   10544      * through a thunk.
   10545      */
   10546     void sendAfterTextChanged(Editable text) {
   10547         if (mListeners != null) {
   10548             final ArrayList<TextWatcher> list = mListeners;
   10549             final int count = list.size();
   10550             for (int i = 0; i < count; i++) {
   10551                 list.get(i).afterTextChanged(text);
   10552             }
   10553         }
   10554 
   10555         notifyListeningManagersAfterTextChanged();
   10556 
   10557         hideErrorIfUnchanged();
   10558     }
   10559 
   10560     /**
   10561      * Notify managers (such as {@link AutofillManager}) that are interested in text changes.
   10562      */
   10563     private void notifyListeningManagersAfterTextChanged() {
   10564 
   10565         // Autofill
   10566         if (isAutofillable()) {
   10567             // It is important to not check whether the view is important for autofill
   10568             // since the user can trigger autofill manually on not important views.
   10569             final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
   10570             if (afm != null) {
   10571                 if (android.view.autofill.Helper.sVerbose) {
   10572                     Log.v(LOG_TAG, "notifyAutoFillManagerAfterTextChanged");
   10573                 }
   10574                 afm.notifyValueChanged(TextView.this);
   10575             }
   10576         }
   10577     }
   10578 
   10579     private boolean isAutofillable() {
   10580         // It is important to not check whether the view is important for autofill
   10581         // since the user can trigger autofill manually on not important views.
   10582         return getAutofillType() != AUTOFILL_TYPE_NONE;
   10583     }
   10584 
   10585     void updateAfterEdit() {
   10586         invalidate();
   10587         int curs = getSelectionStart();
   10588 
   10589         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
   10590             registerForPreDraw();
   10591         }
   10592 
   10593         checkForResize();
   10594 
   10595         if (curs >= 0) {
   10596             mHighlightPathBogus = true;
   10597             if (mEditor != null) mEditor.makeBlink();
   10598             bringPointIntoView(curs);
   10599         }
   10600     }
   10601 
   10602     /**
   10603      * Not private so it can be called from an inner class without going
   10604      * through a thunk.
   10605      */
   10606     void handleTextChanged(CharSequence buffer, int start, int before, int after) {
   10607         sLastCutCopyOrTextChangedTime = 0;
   10608 
   10609         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
   10610         if (ims == null || ims.mBatchEditNesting == 0) {
   10611             updateAfterEdit();
   10612         }
   10613         if (ims != null) {
   10614             ims.mContentChanged = true;
   10615             if (ims.mChangedStart < 0) {
   10616                 ims.mChangedStart = start;
   10617                 ims.mChangedEnd = start + before;
   10618             } else {
   10619                 ims.mChangedStart = Math.min(ims.mChangedStart, start);
   10620                 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
   10621             }
   10622             ims.mChangedDelta += after - before;
   10623         }
   10624         resetErrorChangedFlag();
   10625         sendOnTextChanged(buffer, start, before, after);
   10626         onTextChanged(buffer, start, before, after);
   10627     }
   10628 
   10629     /**
   10630      * Not private so it can be called from an inner class without going
   10631      * through a thunk.
   10632      */
   10633     void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
   10634         // XXX Make the start and end move together if this ends up
   10635         // spending too much time invalidating.
   10636 
   10637         boolean selChanged = false;
   10638         int newSelStart = -1, newSelEnd = -1;
   10639 
   10640         final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
   10641 
   10642         if (what == Selection.SELECTION_END) {
   10643             selChanged = true;
   10644             newSelEnd = newStart;
   10645 
   10646             if (oldStart >= 0 || newStart >= 0) {
   10647                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
   10648                 checkForResize();
   10649                 registerForPreDraw();
   10650                 if (mEditor != null) mEditor.makeBlink();
   10651             }
   10652         }
   10653 
   10654         if (what == Selection.SELECTION_START) {
   10655             selChanged = true;
   10656             newSelStart = newStart;
   10657 
   10658             if (oldStart >= 0 || newStart >= 0) {
   10659                 int end = Selection.getSelectionEnd(buf);
   10660                 invalidateCursor(end, oldStart, newStart);
   10661             }
   10662         }
   10663 
   10664         if (selChanged) {
   10665             mHighlightPathBogus = true;
   10666             if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
   10667 
   10668             if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
   10669                 if (newSelStart < 0) {
   10670                     newSelStart = Selection.getSelectionStart(buf);
   10671                 }
   10672                 if (newSelEnd < 0) {
   10673                     newSelEnd = Selection.getSelectionEnd(buf);
   10674                 }
   10675 
   10676                 if (mEditor != null) {
   10677                     mEditor.refreshTextActionMode();
   10678                     if (!hasSelection()
   10679                             && mEditor.getTextActionMode() == null && hasTransientState()) {
   10680                         // User generated selection has been removed.
   10681                         setHasTransientState(false);
   10682                     }
   10683                 }
   10684                 onSelectionChanged(newSelStart, newSelEnd);
   10685             }
   10686         }
   10687 
   10688         if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
   10689                 || what instanceof CharacterStyle) {
   10690             if (ims == null || ims.mBatchEditNesting == 0) {
   10691                 invalidate();
   10692                 mHighlightPathBogus = true;
   10693                 checkForResize();
   10694             } else {
   10695                 ims.mContentChanged = true;
   10696             }
   10697             if (mEditor != null) {
   10698                 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
   10699                 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
   10700                 mEditor.invalidateHandlesAndActionMode();
   10701             }
   10702         }
   10703 
   10704         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
   10705             mHighlightPathBogus = true;
   10706             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
   10707                 ims.mSelectionModeChanged = true;
   10708             }
   10709 
   10710             if (Selection.getSelectionStart(buf) >= 0) {
   10711                 if (ims == null || ims.mBatchEditNesting == 0) {
   10712                     invalidateCursor();
   10713                 } else {
   10714                     ims.mCursorChanged = true;
   10715                 }
   10716             }
   10717         }
   10718 
   10719         if (what instanceof ParcelableSpan) {
   10720             // If this is a span that can be sent to a remote process,
   10721             // the current extract editor would be interested in it.
   10722             if (ims != null && ims.mExtractedTextRequest != null) {
   10723                 if (ims.mBatchEditNesting != 0) {
   10724                     if (oldStart >= 0) {
   10725                         if (ims.mChangedStart > oldStart) {
   10726                             ims.mChangedStart = oldStart;
   10727                         }
   10728                         if (ims.mChangedStart > oldEnd) {
   10729                             ims.mChangedStart = oldEnd;
   10730                         }
   10731                     }
   10732                     if (newStart >= 0) {
   10733                         if (ims.mChangedStart > newStart) {
   10734                             ims.mChangedStart = newStart;
   10735                         }
   10736                         if (ims.mChangedStart > newEnd) {
   10737                             ims.mChangedStart = newEnd;
   10738                         }
   10739                     }
   10740                 } else {
   10741                     if (DEBUG_EXTRACT) {
   10742                         Log.v(LOG_TAG, "Span change outside of batch: "
   10743                                 + oldStart + "-" + oldEnd + ","
   10744                                 + newStart + "-" + newEnd + " " + what);
   10745                     }
   10746                     ims.mContentChanged = true;
   10747                 }
   10748             }
   10749         }
   10750 
   10751         if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
   10752                 && what instanceof SpellCheckSpan) {
   10753             mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
   10754         }
   10755     }
   10756 
   10757     @Override
   10758     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
   10759         if (isTemporarilyDetached()) {
   10760             // If we are temporarily in the detach state, then do nothing.
   10761             super.onFocusChanged(focused, direction, previouslyFocusedRect);
   10762             return;
   10763         }
   10764 
   10765         if (mEditor != null) mEditor.onFocusChanged(focused, direction);
   10766 
   10767         if (focused) {
   10768             if (mSpannable != null) {
   10769                 MetaKeyKeyListener.resetMetaState(mSpannable);
   10770             }
   10771         }
   10772 
   10773         startStopMarquee(focused);
   10774 
   10775         if (mTransformation != null) {
   10776             mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
   10777         }
   10778 
   10779         super.onFocusChanged(focused, direction, previouslyFocusedRect);
   10780     }
   10781 
   10782     @Override
   10783     public void onWindowFocusChanged(boolean hasWindowFocus) {
   10784         super.onWindowFocusChanged(hasWindowFocus);
   10785 
   10786         if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
   10787 
   10788         startStopMarquee(hasWindowFocus);
   10789     }
   10790 
   10791     @Override
   10792     protected void onVisibilityChanged(View changedView, int visibility) {
   10793         super.onVisibilityChanged(changedView, visibility);
   10794         if (mEditor != null && visibility != VISIBLE) {
   10795             mEditor.hideCursorAndSpanControllers();
   10796             stopTextActionMode();
   10797         }
   10798     }
   10799 
   10800     /**
   10801      * Use {@link BaseInputConnection#removeComposingSpans
   10802      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
   10803      * state from this text view.
   10804      */
   10805     public void clearComposingText() {
   10806         if (mText instanceof Spannable) {
   10807             BaseInputConnection.removeComposingSpans(mSpannable);
   10808         }
   10809     }
   10810 
   10811     @Override
   10812     public void setSelected(boolean selected) {
   10813         boolean wasSelected = isSelected();
   10814 
   10815         super.setSelected(selected);
   10816 
   10817         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
   10818             if (selected) {
   10819                 startMarquee();
   10820             } else {
   10821                 stopMarquee();
   10822             }
   10823         }
   10824     }
   10825 
   10826     @Override
   10827     public boolean onTouchEvent(MotionEvent event) {
   10828         final int action = event.getActionMasked();
   10829         if (mEditor != null) {
   10830             mEditor.onTouchEvent(event);
   10831 
   10832             if (mEditor.mSelectionModifierCursorController != null
   10833                     && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
   10834                 return true;
   10835             }
   10836         }
   10837 
   10838         final boolean superResult = super.onTouchEvent(event);
   10839 
   10840         /*
   10841          * Don't handle the release after a long press, because it will move the selection away from
   10842          * whatever the menu action was trying to affect. If the long press should have triggered an
   10843          * insertion action mode, we can now actually show it.
   10844          */
   10845         if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
   10846             mEditor.mDiscardNextActionUp = false;
   10847 
   10848             if (mEditor.mIsInsertionActionModeStartPending) {
   10849                 mEditor.startInsertionActionMode();
   10850                 mEditor.mIsInsertionActionModeStartPending = false;
   10851             }
   10852             return superResult;
   10853         }
   10854 
   10855         final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
   10856                 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
   10857 
   10858         if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
   10859                 && mText instanceof Spannable && mLayout != null) {
   10860             boolean handled = false;
   10861 
   10862             if (mMovement != null) {
   10863                 handled |= mMovement.onTouchEvent(this, mSpannable, event);
   10864             }
   10865 
   10866             final boolean textIsSelectable = isTextSelectable();
   10867             if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
   10868                 // The LinkMovementMethod which should handle taps on links has not been installed
   10869                 // on non editable text that support text selection.
   10870                 // We reproduce its behavior here to open links for these.
   10871                 ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
   10872                     getSelectionEnd(), ClickableSpan.class);
   10873 
   10874                 if (links.length > 0) {
   10875                     links[0].onClick(this);
   10876                     handled = true;
   10877                 }
   10878             }
   10879 
   10880             if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
   10881                 // Show the IME, except when selecting in read-only text.
   10882                 final InputMethodManager imm = getInputMethodManager();
   10883                 viewClicked(imm);
   10884                 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
   10885                     imm.showSoftInput(this, 0);
   10886                 }
   10887 
   10888                 // The above condition ensures that the mEditor is not null
   10889                 mEditor.onTouchUpEvent(event);
   10890 
   10891                 handled = true;
   10892             }
   10893 
   10894             if (handled) {
   10895                 return true;
   10896             }
   10897         }
   10898 
   10899         return superResult;
   10900     }
   10901 
   10902     @Override
   10903     public boolean onGenericMotionEvent(MotionEvent event) {
   10904         if (mMovement != null && mText instanceof Spannable && mLayout != null) {
   10905             try {
   10906                 if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
   10907                     return true;
   10908                 }
   10909             } catch (AbstractMethodError ex) {
   10910                 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
   10911                 // Ignore its absence in case third party applications implemented the
   10912                 // interface directly.
   10913             }
   10914         }
   10915         return super.onGenericMotionEvent(event);
   10916     }
   10917 
   10918     @Override
   10919     protected void onCreateContextMenu(ContextMenu menu) {
   10920         if (mEditor != null) {
   10921             mEditor.onCreateContextMenu(menu);
   10922         }
   10923     }
   10924 
   10925     @Override
   10926     public boolean showContextMenu() {
   10927         if (mEditor != null) {
   10928             mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
   10929         }
   10930         return super.showContextMenu();
   10931     }
   10932 
   10933     @Override
   10934     public boolean showContextMenu(float x, float y) {
   10935         if (mEditor != null) {
   10936             mEditor.setContextMenuAnchor(x, y);
   10937         }
   10938         return super.showContextMenu(x, y);
   10939     }
   10940 
   10941     /**
   10942      * @return True iff this TextView contains a text that can be edited, or if this is
   10943      * a selectable TextView.
   10944      */
   10945     @UnsupportedAppUsage
   10946     boolean isTextEditable() {
   10947         return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
   10948     }
   10949 
   10950     /**
   10951      * Returns true, only while processing a touch gesture, if the initial
   10952      * touch down event caused focus to move to the text view and as a result
   10953      * its selection changed.  Only valid while processing the touch gesture
   10954      * of interest, in an editable text view.
   10955      */
   10956     public boolean didTouchFocusSelect() {
   10957         return mEditor != null && mEditor.mTouchFocusSelected;
   10958     }
   10959 
   10960     @Override
   10961     public void cancelLongPress() {
   10962         super.cancelLongPress();
   10963         if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
   10964     }
   10965 
   10966     @Override
   10967     public boolean onTrackballEvent(MotionEvent event) {
   10968         if (mMovement != null && mSpannable != null && mLayout != null) {
   10969             if (mMovement.onTrackballEvent(this, mSpannable, event)) {
   10970                 return true;
   10971             }
   10972         }
   10973 
   10974         return super.onTrackballEvent(event);
   10975     }
   10976 
   10977     /**
   10978      * Sets the Scroller used for producing a scrolling animation
   10979      *
   10980      * @param s A Scroller instance
   10981      */
   10982     public void setScroller(Scroller s) {
   10983         mScroller = s;
   10984     }
   10985 
   10986     @Override
   10987     protected float getLeftFadingEdgeStrength() {
   10988         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
   10989             final Marquee marquee = mMarquee;
   10990             if (marquee.shouldDrawLeftFade()) {
   10991                 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
   10992             } else {
   10993                 return 0.0f;
   10994             }
   10995         } else if (getLineCount() == 1) {
   10996             final float lineLeft = getLayout().getLineLeft(0);
   10997             if (lineLeft > mScrollX) return 0.0f;
   10998             return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
   10999         }
   11000         return super.getLeftFadingEdgeStrength();
   11001     }
   11002 
   11003     @Override
   11004     protected float getRightFadingEdgeStrength() {
   11005         if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
   11006             final Marquee marquee = mMarquee;
   11007             return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
   11008         } else if (getLineCount() == 1) {
   11009             final float rightEdge = mScrollX +
   11010                     (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
   11011             final float lineRight = getLayout().getLineRight(0);
   11012             if (lineRight < rightEdge) return 0.0f;
   11013             return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
   11014         }
   11015         return super.getRightFadingEdgeStrength();
   11016     }
   11017 
   11018     /**
   11019      * Calculates the fading edge strength as the ratio of the distance between two
   11020      * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
   11021      * value for the distance calculation.
   11022      *
   11023      * @param position1 A horizontal position.
   11024      * @param position2 A horizontal position.
   11025      * @return Fading edge strength between [0.0f, 1.0f].
   11026      */
   11027     @FloatRange(from = 0.0, to = 1.0)
   11028     private float getHorizontalFadingEdgeStrength(float position1, float position2) {
   11029         final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
   11030         if (horizontalFadingEdgeLength == 0) return 0.0f;
   11031         final float diff = Math.abs(position1 - position2);
   11032         if (diff > horizontalFadingEdgeLength) return 1.0f;
   11033         return diff / horizontalFadingEdgeLength;
   11034     }
   11035 
   11036     private boolean isMarqueeFadeEnabled() {
   11037         return mEllipsize == TextUtils.TruncateAt.MARQUEE
   11038                 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
   11039     }
   11040 
   11041     @Override
   11042     protected int computeHorizontalScrollRange() {
   11043         if (mLayout != null) {
   11044             return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
   11045                     ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
   11046         }
   11047 
   11048         return super.computeHorizontalScrollRange();
   11049     }
   11050 
   11051     @Override
   11052     protected int computeVerticalScrollRange() {
   11053         if (mLayout != null) {
   11054             return mLayout.getHeight();
   11055         }
   11056         return super.computeVerticalScrollRange();
   11057     }
   11058 
   11059     @Override
   11060     protected int computeVerticalScrollExtent() {
   11061         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
   11062     }
   11063 
   11064     @Override
   11065     public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
   11066         super.findViewsWithText(outViews, searched, flags);
   11067         if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
   11068                 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
   11069             String searchedLowerCase = searched.toString().toLowerCase();
   11070             String textLowerCase = mText.toString().toLowerCase();
   11071             if (textLowerCase.contains(searchedLowerCase)) {
   11072                 outViews.add(this);
   11073             }
   11074         }
   11075     }
   11076 
   11077     /**
   11078      * Type of the text buffer that defines the characteristics of the text such as static,
   11079      * styleable, or editable.
   11080      */
   11081     public enum BufferType {
   11082         NORMAL, SPANNABLE, EDITABLE
   11083     }
   11084 
   11085     /**
   11086      * Returns the TextView_textColor attribute from the TypedArray, if set, or
   11087      * the TextAppearance_textColor from the TextView_textAppearance attribute,
   11088      * if TextView_textColor was not set directly.
   11089      *
   11090      * @removed
   11091      */
   11092     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
   11093         if (attrs == null) {
   11094             // Preserve behavior prior to removal of this API.
   11095             throw new NullPointerException();
   11096         }
   11097 
   11098         // It's not safe to use this method from apps. The parameter 'attrs'
   11099         // must have been obtained using the TextView filter array which is not
   11100         // available to the SDK. As such, we grab a default TypedArray with the
   11101         // right filter instead here.
   11102         final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
   11103         ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
   11104         if (colors == null) {
   11105             final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
   11106             if (ap != 0) {
   11107                 final TypedArray appearance = context.obtainStyledAttributes(
   11108                         ap, R.styleable.TextAppearance);
   11109                 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
   11110                 appearance.recycle();
   11111             }
   11112         }
   11113         a.recycle();
   11114 
   11115         return colors;
   11116     }
   11117 
   11118     /**
   11119      * Returns the default color from the TextView_textColor attribute from the
   11120      * AttributeSet, if set, or the default color from the
   11121      * TextAppearance_textColor from the TextView_textAppearance attribute, if
   11122      * TextView_textColor was not set directly.
   11123      *
   11124      * @removed
   11125      */
   11126     public static int getTextColor(Context context, TypedArray attrs, int def) {
   11127         final ColorStateList colors = getTextColors(context, attrs);
   11128         if (colors == null) {
   11129             return def;
   11130         } else {
   11131             return colors.getDefaultColor();
   11132         }
   11133     }
   11134 
   11135     @Override
   11136     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
   11137         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
   11138             // Handle Ctrl-only shortcuts.
   11139             switch (keyCode) {
   11140                 case KeyEvent.KEYCODE_A:
   11141                     if (canSelectText()) {
   11142                         return onTextContextMenuItem(ID_SELECT_ALL);
   11143                     }
   11144                     break;
   11145                 case KeyEvent.KEYCODE_Z:
   11146                     if (canUndo()) {
   11147                         return onTextContextMenuItem(ID_UNDO);
   11148                     }
   11149                     break;
   11150                 case KeyEvent.KEYCODE_X:
   11151                     if (canCut()) {
   11152                         return onTextContextMenuItem(ID_CUT);
   11153                     }
   11154                     break;
   11155                 case KeyEvent.KEYCODE_C:
   11156                     if (canCopy()) {
   11157                         return onTextContextMenuItem(ID_COPY);
   11158                     }
   11159                     break;
   11160                 case KeyEvent.KEYCODE_V:
   11161                     if (canPaste()) {
   11162                         return onTextContextMenuItem(ID_PASTE);
   11163                     }
   11164                     break;
   11165             }
   11166         } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
   11167             // Handle Ctrl-Shift shortcuts.
   11168             switch (keyCode) {
   11169                 case KeyEvent.KEYCODE_Z:
   11170                     if (canRedo()) {
   11171                         return onTextContextMenuItem(ID_REDO);
   11172                     }
   11173                     break;
   11174                 case KeyEvent.KEYCODE_V:
   11175                     if (canPaste()) {
   11176                         return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
   11177                     }
   11178             }
   11179         }
   11180         return super.onKeyShortcut(keyCode, event);
   11181     }
   11182 
   11183     /**
   11184      * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
   11185      * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
   11186      * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
   11187      * sufficient.
   11188      */
   11189     boolean canSelectText() {
   11190         return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
   11191     }
   11192 
   11193     /**
   11194      * Test based on the <i>intrinsic</i> charateristics of the TextView.
   11195      * The text must be spannable and the movement method must allow for arbitary selection.
   11196      *
   11197      * See also {@link #canSelectText()}.
   11198      */
   11199     boolean textCanBeSelected() {
   11200         // prepareCursorController() relies on this method.
   11201         // If you change this condition, make sure prepareCursorController is called anywhere
   11202         // the value of this condition might be changed.
   11203         if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
   11204         return isTextEditable()
   11205                 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
   11206     }
   11207 
   11208     @UnsupportedAppUsage
   11209     private Locale getTextServicesLocale(boolean allowNullLocale) {
   11210         // Start fetching the text services locale asynchronously.
   11211         updateTextServicesLocaleAsync();
   11212         // If !allowNullLocale and there is no cached text services locale, just return the default
   11213         // locale.
   11214         return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
   11215                 : mCurrentSpellCheckerLocaleCache;
   11216     }
   11217 
   11218     /**
   11219      * Associate {@link UserHandle} who is considered to be the logical owner of the text shown in
   11220      * this {@link TextView}.
   11221      *
   11222      * <p>Most of applications should not worry about this.  Some privileged apps that host UI for
   11223      * other apps may need to set this so that the system can user right user's resources and
   11224      * services such as input methods and spell checkers.</p>
   11225      *
   11226      * @param user {@link UserHandle} who is considered to be the owner of the text shown in this
   11227      *        {@link TextView}. {@code null} to reset {@link #mTextOperationUser}.
   11228      * @hide
   11229      */
   11230     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
   11231     public final void setTextOperationUser(@Nullable UserHandle user) {
   11232         if (Objects.equals(mTextOperationUser, user)) {
   11233             return;
   11234         }
   11235         if (user != null && !Process.myUserHandle().equals(user)) {
   11236             // Just for preventing people from accidentally using this hidden API without
   11237             // the required permission.  The same permission is also checked in the system server.
   11238             if (getContext().checkSelfPermission(INTERACT_ACROSS_USERS_FULL)
   11239                     != PackageManager.PERMISSION_GRANTED) {
   11240                 throw new SecurityException("INTERACT_ACROSS_USERS_FULL is required."
   11241                         + " userId=" + user.getIdentifier()
   11242                         + " callingUserId" + UserHandle.myUserId());
   11243             }
   11244         }
   11245         mTextOperationUser = user;
   11246         // Invalidate some resources
   11247         mCurrentSpellCheckerLocaleCache = null;
   11248         if (mEditor != null) {
   11249             mEditor.onTextOperationUserChanged();
   11250         }
   11251     }
   11252 
   11253     @Nullable
   11254     final TextServicesManager getTextServicesManagerForUser() {
   11255         return getServiceManagerForUser("android", TextServicesManager.class);
   11256     }
   11257 
   11258     @Nullable
   11259     final ClipboardManager getClipboardManagerForUser() {
   11260         return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class);
   11261     }
   11262 
   11263     @Nullable
   11264     final <T> T getServiceManagerForUser(String packageName, Class<T> managerClazz) {
   11265         if (mTextOperationUser == null) {
   11266             return getContext().getSystemService(managerClazz);
   11267         }
   11268         try {
   11269             Context context = getContext().createPackageContextAsUser(
   11270                     packageName, 0 /* flags */, mTextOperationUser);
   11271             return context.getSystemService(managerClazz);
   11272         } catch (PackageManager.NameNotFoundException e) {
   11273             return null;
   11274         }
   11275     }
   11276 
   11277     /**
   11278      * Starts {@link Activity} as a text-operation user if it is specified with
   11279      * {@link #setTextOperationUser(UserHandle)}.
   11280      *
   11281      * <p>Otherwise, just starts {@link Activity} with {@link Context#startActivity(Intent)}.</p>
   11282      *
   11283      * @param intent The description of the activity to start.
   11284      */
   11285     void startActivityAsTextOperationUserIfNecessary(@NonNull Intent intent) {
   11286         if (mTextOperationUser != null) {
   11287             getContext().startActivityAsUser(intent, mTextOperationUser);
   11288         } else {
   11289             getContext().startActivity(intent);
   11290         }
   11291     }
   11292 
   11293     /**
   11294      * This is a temporary method. Future versions may support multi-locale text.
   11295      * Caveat: This method may not return the latest text services locale, but this should be
   11296      * acceptable and it's more important to make this method asynchronous.
   11297      *
   11298      * @return The locale that should be used for a word iterator
   11299      * in this TextView, based on the current spell checker settings,
   11300      * the current IME's locale, or the system default locale.
   11301      * Please note that a word iterator in this TextView is different from another word iterator
   11302      * used by SpellChecker.java of TextView. This method should be used for the former.
   11303      * @hide
   11304      */
   11305     // TODO: Support multi-locale
   11306     // TODO: Update the text services locale immediately after the keyboard locale is switched
   11307     // by catching intent of keyboard switch event
   11308     public Locale getTextServicesLocale() {
   11309         return getTextServicesLocale(false /* allowNullLocale */);
   11310     }
   11311 
   11312     /**
   11313      * @return {@code true} if this TextView is specialized for showing and interacting with the
   11314      * extracted text in a full-screen input method.
   11315      * @hide
   11316      */
   11317     public boolean isInExtractedMode() {
   11318         return false;
   11319     }
   11320 
   11321     /**
   11322      * @return {@code true} if this widget supports auto-sizing text and has been configured to
   11323      * auto-size.
   11324      */
   11325     private boolean isAutoSizeEnabled() {
   11326         return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
   11327     }
   11328 
   11329     /**
   11330      * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
   11331      * @hide
   11332      */
   11333     protected boolean supportsAutoSizeText() {
   11334         return true;
   11335     }
   11336 
   11337     /**
   11338      * This is a temporary method. Future versions may support multi-locale text.
   11339      * Caveat: This method may not return the latest spell checker locale, but this should be
   11340      * acceptable and it's more important to make this method asynchronous.
   11341      *
   11342      * @return The locale that should be used for a spell checker in this TextView,
   11343      * based on the current spell checker settings, the current IME's locale, or the system default
   11344      * locale.
   11345      * @hide
   11346      */
   11347     public Locale getSpellCheckerLocale() {
   11348         return getTextServicesLocale(true /* allowNullLocale */);
   11349     }
   11350 
   11351     private void updateTextServicesLocaleAsync() {
   11352         // AsyncTask.execute() uses a serial executor which means we don't have
   11353         // to lock around updateTextServicesLocaleLocked() to prevent it from
   11354         // being executed n times in parallel.
   11355         AsyncTask.execute(new Runnable() {
   11356             @Override
   11357             public void run() {
   11358                 updateTextServicesLocaleLocked();
   11359             }
   11360         });
   11361     }
   11362 
   11363     @UnsupportedAppUsage
   11364     private void updateTextServicesLocaleLocked() {
   11365         final TextServicesManager textServicesManager = getTextServicesManagerForUser();
   11366         if (textServicesManager == null) {
   11367             return;
   11368         }
   11369         final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
   11370         final Locale locale;
   11371         if (subtype != null) {
   11372             locale = subtype.getLocaleObject();
   11373         } else {
   11374             locale = null;
   11375         }
   11376         mCurrentSpellCheckerLocaleCache = locale;
   11377     }
   11378 
   11379     void onLocaleChanged() {
   11380         mEditor.onLocaleChanged();
   11381     }
   11382 
   11383     /**
   11384      * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
   11385      * Made available to achieve a consistent behavior.
   11386      * @hide
   11387      */
   11388     public WordIterator getWordIterator() {
   11389         if (mEditor != null) {
   11390             return mEditor.getWordIterator();
   11391         } else {
   11392             return null;
   11393         }
   11394     }
   11395 
   11396     /** @hide */
   11397     @Override
   11398     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
   11399         super.onPopulateAccessibilityEventInternal(event);
   11400 
   11401         final CharSequence text = getTextForAccessibility();
   11402         if (!TextUtils.isEmpty(text)) {
   11403             event.getText().add(text);
   11404         }
   11405     }
   11406 
   11407     @Override
   11408     public CharSequence getAccessibilityClassName() {
   11409         return TextView.class.getName();
   11410     }
   11411 
   11412     /** @hide */
   11413     @Override
   11414     protected void onProvideStructure(@NonNull ViewStructure structure,
   11415             @ViewStructureType int viewFor, int flags) {
   11416         super.onProvideStructure(structure, viewFor, flags);
   11417 
   11418         final boolean isPassword = hasPasswordTransformationMethod()
   11419                 || isPasswordInputType(getInputType());
   11420         if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11421             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11422                 structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
   11423             }
   11424             if (mTextId != Resources.ID_NULL) {
   11425                 try {
   11426                     structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
   11427                 } catch (Resources.NotFoundException e) {
   11428                     if (android.view.autofill.Helper.sVerbose) {
   11429                         Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
   11430                                 + mTextId + ": " + e.getMessage());
   11431                     }
   11432                 }
   11433             }
   11434         }
   11435 
   11436         if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11437             if (mLayout == null) {
   11438                 assumeLayout();
   11439             }
   11440             Layout layout = mLayout;
   11441             final int lineCount = layout.getLineCount();
   11442             if (lineCount <= 1) {
   11443                 // Simple case: this is a single line.
   11444                 final CharSequence text = getText();
   11445                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11446                     structure.setText(text);
   11447                 } else {
   11448                     structure.setText(text, getSelectionStart(), getSelectionEnd());
   11449                 }
   11450             } else {
   11451                 // Complex case: multi-line, could be scrolled or within a scroll container
   11452                 // so some lines are not visible.
   11453                 final int[] tmpCords = new int[2];
   11454                 getLocationInWindow(tmpCords);
   11455                 final int topWindowLocation = tmpCords[1];
   11456                 View root = this;
   11457                 ViewParent viewParent = getParent();
   11458                 while (viewParent instanceof View) {
   11459                     root = (View) viewParent;
   11460                     viewParent = root.getParent();
   11461                 }
   11462                 final int windowHeight = root.getHeight();
   11463                 final int topLine;
   11464                 final int bottomLine;
   11465                 if (topWindowLocation >= 0) {
   11466                     // The top of the view is fully within its window; start text at line 0.
   11467                     topLine = getLineAtCoordinateUnclamped(0);
   11468                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
   11469                 } else {
   11470                     // The top of hte window has scrolled off the top of the window; figure out
   11471                     // the starting line for this.
   11472                     topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
   11473                     bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
   11474                 }
   11475                 // We want to return some contextual lines above/below the lines that are
   11476                 // actually visible.
   11477                 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
   11478                 if (expandedTopLine < 0) {
   11479                     expandedTopLine = 0;
   11480                 }
   11481                 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
   11482                 if (expandedBottomLine >= lineCount) {
   11483                     expandedBottomLine = lineCount - 1;
   11484                 }
   11485 
   11486                 // Convert lines into character offsets.
   11487                 int expandedTopChar = layout.getLineStart(expandedTopLine);
   11488                 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
   11489 
   11490                 // Take into account selection -- if there is a selection, we need to expand
   11491                 // the text we are returning to include that selection.
   11492                 final int selStart = getSelectionStart();
   11493                 final int selEnd = getSelectionEnd();
   11494                 if (selStart < selEnd) {
   11495                     if (selStart < expandedTopChar) {
   11496                         expandedTopChar = selStart;
   11497                     }
   11498                     if (selEnd > expandedBottomChar) {
   11499                         expandedBottomChar = selEnd;
   11500                     }
   11501                 }
   11502 
   11503                 // Get the text and trim it to the range we are reporting.
   11504                 CharSequence text = getText();
   11505                 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
   11506                     text = text.subSequence(expandedTopChar, expandedBottomChar);
   11507                 }
   11508 
   11509                 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11510                     structure.setText(text);
   11511                 } else {
   11512                     structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
   11513 
   11514                     final int[] lineOffsets = new int[bottomLine - topLine + 1];
   11515                     final int[] lineBaselines = new int[bottomLine - topLine + 1];
   11516                     final int baselineOffset = getBaselineOffset();
   11517                     for (int i = topLine; i <= bottomLine; i++) {
   11518                         lineOffsets[i - topLine] = layout.getLineStart(i);
   11519                         lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
   11520                     }
   11521                     structure.setTextLines(lineOffsets, lineBaselines);
   11522                 }
   11523             }
   11524 
   11525             if (viewFor == VIEW_STRUCTURE_FOR_ASSIST) {
   11526                 // Extract style information that applies to the TextView as a whole.
   11527                 int style = 0;
   11528                 int typefaceStyle = getTypefaceStyle();
   11529                 if ((typefaceStyle & Typeface.BOLD) != 0) {
   11530                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
   11531                 }
   11532                 if ((typefaceStyle & Typeface.ITALIC) != 0) {
   11533                     style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
   11534                 }
   11535 
   11536                 // Global styles can also be set via TextView.setPaintFlags().
   11537                 int paintFlags = mTextPaint.getFlags();
   11538                 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
   11539                     style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
   11540                 }
   11541                 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
   11542                     style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
   11543                 }
   11544                 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
   11545                     style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
   11546                 }
   11547 
   11548                 // TextView does not have its own text background color. A background is either part
   11549                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
   11550                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
   11551                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
   11552             }
   11553             if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) {
   11554                 structure.setMinTextEms(getMinEms());
   11555                 structure.setMaxTextEms(getMaxEms());
   11556                 int maxLength = -1;
   11557                 for (InputFilter filter: getFilters()) {
   11558                     if (filter instanceof InputFilter.LengthFilter) {
   11559                         maxLength = ((InputFilter.LengthFilter) filter).getMax();
   11560                         break;
   11561                     }
   11562                 }
   11563                 structure.setMaxTextLength(maxLength);
   11564             }
   11565         }
   11566         structure.setHint(getHint());
   11567         structure.setInputType(getInputType());
   11568     }
   11569 
   11570     boolean canRequestAutofill() {
   11571         if (!isAutofillable()) {
   11572             return false;
   11573         }
   11574         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
   11575         if (afm != null) {
   11576             return afm.isEnabled();
   11577         }
   11578         return false;
   11579     }
   11580 
   11581     private void requestAutofill() {
   11582         final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
   11583         if (afm != null) {
   11584             afm.requestAutofill(this);
   11585         }
   11586     }
   11587 
   11588     @Override
   11589     public void autofill(AutofillValue value) {
   11590         if (!value.isText() || !isTextEditable()) {
   11591             Log.w(LOG_TAG, value + " could not be autofilled into " + this);
   11592             return;
   11593         }
   11594 
   11595         final CharSequence autofilledValue = value.getTextValue();
   11596 
   11597         // First autofill it...
   11598         setText(autofilledValue, mBufferType, true, 0);
   11599 
   11600         // ...then move cursor to the end.
   11601         final CharSequence text = getText();
   11602         if ((text instanceof Spannable)) {
   11603             Selection.setSelection((Spannable) text, text.length());
   11604         }
   11605     }
   11606 
   11607     @Override
   11608     public @AutofillType int getAutofillType() {
   11609         return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
   11610     }
   11611 
   11612     /**
   11613      * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
   11614      * {@code char}s if longer.
   11615      *
   11616      * @return current text, {@code null} if the text is not editable
   11617      *
   11618      * @see View#getAutofillValue()
   11619      */
   11620     @Override
   11621     @Nullable
   11622     public AutofillValue getAutofillValue() {
   11623         if (isTextEditable()) {
   11624             final CharSequence text = TextUtils.trimToParcelableSize(getText());
   11625             return AutofillValue.forText(text);
   11626         }
   11627         return null;
   11628     }
   11629 
   11630     /** @hide */
   11631     @Override
   11632     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
   11633         super.onInitializeAccessibilityEventInternal(event);
   11634 
   11635         final boolean isPassword = hasPasswordTransformationMethod();
   11636         event.setPassword(isPassword);
   11637 
   11638         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
   11639             event.setFromIndex(Selection.getSelectionStart(mText));
   11640             event.setToIndex(Selection.getSelectionEnd(mText));
   11641             event.setItemCount(mText.length());
   11642         }
   11643     }
   11644 
   11645     /** @hide */
   11646     @Override
   11647     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   11648         super.onInitializeAccessibilityNodeInfoInternal(info);
   11649 
   11650         final boolean isPassword = hasPasswordTransformationMethod();
   11651         info.setPassword(isPassword);
   11652         info.setText(getTextForAccessibility());
   11653         info.setHintText(mHint);
   11654         info.setShowingHintText(isShowingHint());
   11655 
   11656         if (mBufferType == BufferType.EDITABLE) {
   11657             info.setEditable(true);
   11658             if (isEnabled()) {
   11659                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
   11660             }
   11661         }
   11662 
   11663         if (mEditor != null) {
   11664             info.setInputType(mEditor.mInputType);
   11665 
   11666             if (mEditor.mError != null) {
   11667                 info.setContentInvalid(true);
   11668                 info.setError(mEditor.mError);
   11669             }
   11670         }
   11671 
   11672         if (!TextUtils.isEmpty(mText)) {
   11673             info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
   11674             info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
   11675             info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
   11676                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
   11677                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
   11678                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
   11679                     | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
   11680             info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
   11681             info.setAvailableExtraData(
   11682                     Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
   11683         }
   11684 
   11685         if (isFocused()) {
   11686             if (canCopy()) {
   11687                 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
   11688             }
   11689             if (canPaste()) {
   11690                 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
   11691             }
   11692             if (canCut()) {
   11693                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
   11694             }
   11695             if (canShare()) {
   11696                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
   11697                         ACCESSIBILITY_ACTION_SHARE,
   11698                         getResources().getString(com.android.internal.R.string.share)));
   11699             }
   11700             if (canProcessText()) {  // also implies mEditor is not null.
   11701                 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
   11702             }
   11703         }
   11704 
   11705         // Check for known input filter types.
   11706         final int numFilters = mFilters.length;
   11707         for (int i = 0; i < numFilters; i++) {
   11708             final InputFilter filter = mFilters[i];
   11709             if (filter instanceof InputFilter.LengthFilter) {
   11710                 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
   11711             }
   11712         }
   11713 
   11714         if (!isSingleLine()) {
   11715             info.setMultiLine(true);
   11716         }
   11717     }
   11718 
   11719     @Override
   11720     public void addExtraDataToAccessibilityNodeInfo(
   11721             AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
   11722         // The only extra data we support requires arguments.
   11723         if (arguments == null) {
   11724             return;
   11725         }
   11726         if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
   11727             int positionInfoStartIndex = arguments.getInt(
   11728                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
   11729             int positionInfoLength = arguments.getInt(
   11730                     EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
   11731             if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
   11732                     || (positionInfoStartIndex >= mText.length())) {
   11733                 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
   11734                 return;
   11735             }
   11736             RectF[] boundingRects = new RectF[positionInfoLength];
   11737             final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
   11738             populateCharacterBounds(builder, positionInfoStartIndex,
   11739                     positionInfoStartIndex + positionInfoLength,
   11740                     viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
   11741             CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
   11742             for (int i = 0; i < positionInfoLength; i++) {
   11743                 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
   11744                 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
   11745                     RectF bounds = cursorAnchorInfo
   11746                             .getCharacterBounds(positionInfoStartIndex + i);
   11747                     if (bounds != null) {
   11748                         mapRectFromViewToScreenCoords(bounds, true);
   11749                         boundingRects[i] = bounds;
   11750                     }
   11751                 }
   11752             }
   11753             info.getExtras().putParcelableArray(extraDataKey, boundingRects);
   11754         }
   11755     }
   11756 
   11757     /**
   11758      * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
   11759      *
   11760      * @param builder The builder to populate
   11761      * @param startIndex The starting character index to populate
   11762      * @param endIndex The ending character index to populate
   11763      * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
   11764      * content
   11765      * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
   11766      * @hide
   11767      */
   11768     public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
   11769             int startIndex, int endIndex, float viewportToContentHorizontalOffset,
   11770             float viewportToContentVerticalOffset) {
   11771         final int minLine = mLayout.getLineForOffset(startIndex);
   11772         final int maxLine = mLayout.getLineForOffset(endIndex - 1);
   11773         for (int line = minLine; line <= maxLine; ++line) {
   11774             final int lineStart = mLayout.getLineStart(line);
   11775             final int lineEnd = mLayout.getLineEnd(line);
   11776             final int offsetStart = Math.max(lineStart, startIndex);
   11777             final int offsetEnd = Math.min(lineEnd, endIndex);
   11778             final boolean ltrLine =
   11779                     mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
   11780             final float[] widths = new float[offsetEnd - offsetStart];
   11781             mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths);
   11782             final float top = mLayout.getLineTop(line);
   11783             final float bottom = mLayout.getLineBottom(line);
   11784             for (int offset = offsetStart; offset < offsetEnd; ++offset) {
   11785                 final float charWidth = widths[offset - offsetStart];
   11786                 final boolean isRtl = mLayout.isRtlCharAt(offset);
   11787                 final float primary = mLayout.getPrimaryHorizontal(offset);
   11788                 final float secondary = mLayout.getSecondaryHorizontal(offset);
   11789                 // TODO: This doesn't work perfectly for text with custom styles and
   11790                 // TAB chars.
   11791                 final float left;
   11792                 final float right;
   11793                 if (ltrLine) {
   11794                     if (isRtl) {
   11795                         left = secondary - charWidth;
   11796                         right = secondary;
   11797                     } else {
   11798                         left = primary;
   11799                         right = primary + charWidth;
   11800                     }
   11801                 } else {
   11802                     if (!isRtl) {
   11803                         left = secondary;
   11804                         right = secondary + charWidth;
   11805                     } else {
   11806                         left = primary - charWidth;
   11807                         right = primary;
   11808                     }
   11809                 }
   11810                 // TODO: Check top-right and bottom-left as well.
   11811                 final float localLeft = left + viewportToContentHorizontalOffset;
   11812                 final float localRight = right + viewportToContentHorizontalOffset;
   11813                 final float localTop = top + viewportToContentVerticalOffset;
   11814                 final float localBottom = bottom + viewportToContentVerticalOffset;
   11815                 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
   11816                 final boolean isBottomRightVisible =
   11817                         isPositionVisible(localRight, localBottom);
   11818                 int characterBoundsFlags = 0;
   11819                 if (isTopLeftVisible || isBottomRightVisible) {
   11820                     characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
   11821                 }
   11822                 if (!isTopLeftVisible || !isBottomRightVisible) {
   11823                     characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
   11824                 }
   11825                 if (isRtl) {
   11826                     characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
   11827                 }
   11828                 // Here offset is the index in Java chars.
   11829                 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
   11830                         localBottom, characterBoundsFlags);
   11831             }
   11832         }
   11833     }
   11834 
   11835     /**
   11836      * @hide
   11837      */
   11838     public boolean isPositionVisible(final float positionX, final float positionY) {
   11839         synchronized (TEMP_POSITION) {
   11840             final float[] position = TEMP_POSITION;
   11841             position[0] = positionX;
   11842             position[1] = positionY;
   11843             View view = this;
   11844 
   11845             while (view != null) {
   11846                 if (view != this) {
   11847                     // Local scroll is already taken into account in positionX/Y
   11848                     position[0] -= view.getScrollX();
   11849                     position[1] -= view.getScrollY();
   11850                 }
   11851 
   11852                 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
   11853                         || position[1] > view.getHeight()) {
   11854                     return false;
   11855                 }
   11856 
   11857                 if (!view.getMatrix().isIdentity()) {
   11858                     view.getMatrix().mapPoints(position);
   11859                 }
   11860 
   11861                 position[0] += view.getLeft();
   11862                 position[1] += view.getTop();
   11863 
   11864                 final ViewParent parent = view.getParent();
   11865                 if (parent instanceof View) {
   11866                     view = (View) parent;
   11867                 } else {
   11868                     // We've reached the ViewRoot, stop iterating
   11869                     view = null;
   11870                 }
   11871             }
   11872         }
   11873 
   11874         // We've been able to walk up the view hierarchy and the position was never clipped
   11875         return true;
   11876     }
   11877 
   11878     /**
   11879      * Performs an accessibility action after it has been offered to the
   11880      * delegate.
   11881      *
   11882      * @hide
   11883      */
   11884     @Override
   11885     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   11886         if (mEditor != null
   11887                 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
   11888             return true;
   11889         }
   11890         switch (action) {
   11891             case AccessibilityNodeInfo.ACTION_CLICK: {
   11892                 return performAccessibilityActionClick(arguments);
   11893             }
   11894             case AccessibilityNodeInfo.ACTION_COPY: {
   11895                 if (isFocused() && canCopy()) {
   11896                     if (onTextContextMenuItem(ID_COPY)) {
   11897                         return true;
   11898                     }
   11899                 }
   11900             } return false;
   11901             case AccessibilityNodeInfo.ACTION_PASTE: {
   11902                 if (isFocused() && canPaste()) {
   11903                     if (onTextContextMenuItem(ID_PASTE)) {
   11904                         return true;
   11905                     }
   11906                 }
   11907             } return false;
   11908             case AccessibilityNodeInfo.ACTION_CUT: {
   11909                 if (isFocused() && canCut()) {
   11910                     if (onTextContextMenuItem(ID_CUT)) {
   11911                         return true;
   11912                     }
   11913                 }
   11914             } return false;
   11915             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
   11916                 ensureIterableTextForAccessibilitySelectable();
   11917                 CharSequence text = getIterableTextForAccessibility();
   11918                 if (text == null) {
   11919                     return false;
   11920                 }
   11921                 final int start = (arguments != null) ? arguments.getInt(
   11922                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
   11923                 final int end = (arguments != null) ? arguments.getInt(
   11924                         AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
   11925                 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
   11926                     // No arguments clears the selection.
   11927                     if (start == end && end == -1) {
   11928                         Selection.removeSelection((Spannable) text);
   11929                         return true;
   11930                     }
   11931                     if (start >= 0 && start <= end && end <= text.length()) {
   11932                         Selection.setSelection((Spannable) text, start, end);
   11933                         // Make sure selection mode is engaged.
   11934                         if (mEditor != null) {
   11935                             mEditor.startSelectionActionModeAsync(false);
   11936                         }
   11937                         return true;
   11938                     }
   11939                 }
   11940             } return false;
   11941             case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
   11942             case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
   11943                 ensureIterableTextForAccessibilitySelectable();
   11944                 return super.performAccessibilityActionInternal(action, arguments);
   11945             }
   11946             case ACCESSIBILITY_ACTION_SHARE: {
   11947                 if (isFocused() && canShare()) {
   11948                     if (onTextContextMenuItem(ID_SHARE)) {
   11949                         return true;
   11950                     }
   11951                 }
   11952             } return false;
   11953             case AccessibilityNodeInfo.ACTION_SET_TEXT: {
   11954                 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
   11955                     return false;
   11956                 }
   11957                 CharSequence text = (arguments != null) ? arguments.getCharSequence(
   11958                         AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
   11959                 setText(text);
   11960                 if (mText != null) {
   11961                     int updatedTextLength = mText.length();
   11962                     if (updatedTextLength > 0) {
   11963                         Selection.setSelection(mSpannable, updatedTextLength);
   11964                     }
   11965                 }
   11966             } return true;
   11967             default: {
   11968                 return super.performAccessibilityActionInternal(action, arguments);
   11969             }
   11970         }
   11971     }
   11972 
   11973     private boolean performAccessibilityActionClick(Bundle arguments) {
   11974         boolean handled = false;
   11975 
   11976         if (!isEnabled()) {
   11977             return false;
   11978         }
   11979 
   11980         if (isClickable() || isLongClickable()) {
   11981             // Simulate View.onTouchEvent for an ACTION_UP event
   11982             if (isFocusable() && !isFocused()) {
   11983                 requestFocus();
   11984             }
   11985 
   11986             performClick();
   11987             handled = true;
   11988         }
   11989 
   11990         // Show the IME, except when selecting in read-only text.
   11991         if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
   11992                 && (isTextEditable() || isTextSelectable()) && isFocused()) {
   11993             final InputMethodManager imm = getInputMethodManager();
   11994             viewClicked(imm);
   11995             if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
   11996                 handled |= imm.showSoftInput(this, 0);
   11997             }
   11998         }
   11999 
   12000         return handled;
   12001     }
   12002 
   12003     private boolean hasSpannableText() {
   12004         return mText != null && mText instanceof Spannable;
   12005     }
   12006 
   12007     /** @hide */
   12008     @Override
   12009     public void sendAccessibilityEventInternal(int eventType) {
   12010         if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
   12011             mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
   12012         }
   12013 
   12014         super.sendAccessibilityEventInternal(eventType);
   12015     }
   12016 
   12017     @Override
   12018     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
   12019         // Do not send scroll events since first they are not interesting for
   12020         // accessibility and second such events a generated too frequently.
   12021         // For details see the implementation of bringTextIntoView().
   12022         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
   12023             return;
   12024         }
   12025         super.sendAccessibilityEventUnchecked(event);
   12026     }
   12027 
   12028     /**
   12029      * Returns the text that should be exposed to accessibility services.
   12030      * <p>
   12031      * This approximates what is displayed visually. If the user has specified
   12032      * that accessibility services should speak passwords, this method will
   12033      * bypass any password transformation method and return unobscured text.
   12034      *
   12035      * @return the text that should be exposed to accessibility services, may
   12036      *         be {@code null} if no text is set
   12037      */
   12038     @Nullable
   12039     @UnsupportedAppUsage
   12040     private CharSequence getTextForAccessibility() {
   12041         // If the text is empty, we must be showing the hint text.
   12042         if (TextUtils.isEmpty(mText)) {
   12043             return mHint;
   12044         }
   12045 
   12046         // Otherwise, return whatever text is being displayed.
   12047         return TextUtils.trimToParcelableSize(mTransformed);
   12048     }
   12049 
   12050     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
   12051             int fromIndex, int removedCount, int addedCount) {
   12052         AccessibilityEvent event =
   12053                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
   12054         event.setFromIndex(fromIndex);
   12055         event.setRemovedCount(removedCount);
   12056         event.setAddedCount(addedCount);
   12057         event.setBeforeText(beforeText);
   12058         sendAccessibilityEventUnchecked(event);
   12059     }
   12060 
   12061     private InputMethodManager getInputMethodManager() {
   12062         return getContext().getSystemService(InputMethodManager.class);
   12063     }
   12064 
   12065     /**
   12066      * Returns whether this text view is a current input method target.  The
   12067      * default implementation just checks with {@link InputMethodManager}.
   12068      * @return True if the TextView is a current input method target; false otherwise.
   12069      */
   12070     public boolean isInputMethodTarget() {
   12071         InputMethodManager imm = getInputMethodManager();
   12072         return imm != null && imm.isActive(this);
   12073     }
   12074 
   12075     static final int ID_SELECT_ALL = android.R.id.selectAll;
   12076     static final int ID_UNDO = android.R.id.undo;
   12077     static final int ID_REDO = android.R.id.redo;
   12078     static final int ID_CUT = android.R.id.cut;
   12079     static final int ID_COPY = android.R.id.copy;
   12080     static final int ID_PASTE = android.R.id.paste;
   12081     static final int ID_SHARE = android.R.id.shareText;
   12082     static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
   12083     static final int ID_REPLACE = android.R.id.replaceText;
   12084     static final int ID_ASSIST = android.R.id.textAssist;
   12085     static final int ID_AUTOFILL = android.R.id.autofill;
   12086 
   12087     /**
   12088      * Called when a context menu option for the text view is selected.  Currently
   12089      * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
   12090      * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
   12091      *
   12092      * @return true if the context menu item action was performed.
   12093      */
   12094     public boolean onTextContextMenuItem(int id) {
   12095         int min = 0;
   12096         int max = mText.length();
   12097 
   12098         if (isFocused()) {
   12099             final int selStart = getSelectionStart();
   12100             final int selEnd = getSelectionEnd();
   12101 
   12102             min = Math.max(0, Math.min(selStart, selEnd));
   12103             max = Math.max(0, Math.max(selStart, selEnd));
   12104         }
   12105 
   12106         switch (id) {
   12107             case ID_SELECT_ALL:
   12108                 final boolean hadSelection = hasSelection();
   12109                 selectAllText();
   12110                 if (mEditor != null && hadSelection) {
   12111                     mEditor.invalidateActionModeAsync();
   12112                 }
   12113                 return true;
   12114 
   12115             case ID_UNDO:
   12116                 if (mEditor != null) {
   12117                     mEditor.undo();
   12118                 }
   12119                 return true;  // Returns true even if nothing was undone.
   12120 
   12121             case ID_REDO:
   12122                 if (mEditor != null) {
   12123                     mEditor.redo();
   12124                 }
   12125                 return true;  // Returns true even if nothing was undone.
   12126 
   12127             case ID_PASTE:
   12128                 paste(min, max, true /* withFormatting */);
   12129                 return true;
   12130 
   12131             case ID_PASTE_AS_PLAIN_TEXT:
   12132                 paste(min, max, false /* withFormatting */);
   12133                 return true;
   12134 
   12135             case ID_CUT:
   12136                 final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
   12137                 if (setPrimaryClip(cutData)) {
   12138                     deleteText_internal(min, max);
   12139                 } else {
   12140                     Toast.makeText(getContext(),
   12141                             com.android.internal.R.string.failed_to_copy_to_clipboard,
   12142                             Toast.LENGTH_SHORT).show();
   12143                 }
   12144                 return true;
   12145 
   12146             case ID_COPY:
   12147                 // For link action mode in a non-selectable/non-focusable TextView,
   12148                 // make sure that we set the appropriate min/max.
   12149                 final int selStart = getSelectionStart();
   12150                 final int selEnd = getSelectionEnd();
   12151                 min = Math.max(0, Math.min(selStart, selEnd));
   12152                 max = Math.max(0, Math.max(selStart, selEnd));
   12153                 final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
   12154                 if (setPrimaryClip(copyData)) {
   12155                     stopTextActionMode();
   12156                 } else {
   12157                     Toast.makeText(getContext(),
   12158                             com.android.internal.R.string.failed_to_copy_to_clipboard,
   12159                             Toast.LENGTH_SHORT).show();
   12160                 }
   12161                 return true;
   12162 
   12163             case ID_REPLACE:
   12164                 if (mEditor != null) {
   12165                     mEditor.replace();
   12166                 }
   12167                 return true;
   12168 
   12169             case ID_SHARE:
   12170                 shareSelectedText();
   12171                 return true;
   12172 
   12173             case ID_AUTOFILL:
   12174                 requestAutofill();
   12175                 stopTextActionMode();
   12176                 return true;
   12177         }
   12178         return false;
   12179     }
   12180 
   12181     @UnsupportedAppUsage
   12182     CharSequence getTransformedText(int start, int end) {
   12183         return removeSuggestionSpans(mTransformed.subSequence(start, end));
   12184     }
   12185 
   12186     @Override
   12187     public boolean performLongClick() {
   12188         boolean handled = false;
   12189         boolean performedHapticFeedback = false;
   12190 
   12191         if (mEditor != null) {
   12192             mEditor.mIsBeingLongClicked = true;
   12193         }
   12194 
   12195         if (super.performLongClick()) {
   12196             handled = true;
   12197             performedHapticFeedback = true;
   12198         }
   12199 
   12200         if (mEditor != null) {
   12201             handled |= mEditor.performLongClick(handled);
   12202             mEditor.mIsBeingLongClicked = false;
   12203         }
   12204 
   12205         if (handled) {
   12206             if (!performedHapticFeedback) {
   12207               performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
   12208             }
   12209             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
   12210         } else {
   12211             MetricsLogger.action(
   12212                     mContext,
   12213                     MetricsEvent.TEXT_LONGPRESS,
   12214                     TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
   12215         }
   12216 
   12217         return handled;
   12218     }
   12219 
   12220     @Override
   12221     protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
   12222         super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
   12223         if (mEditor != null) {
   12224             mEditor.onScrollChanged();
   12225         }
   12226     }
   12227 
   12228     /**
   12229      * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
   12230      * by the IME or by the spell checker as the user types. This is done by adding
   12231      * {@link SuggestionSpan}s to the text.
   12232      *
   12233      * When suggestions are enabled (default), this list of suggestions will be displayed when the
   12234      * user asks for them on these parts of the text. This value depends on the inputType of this
   12235      * TextView.
   12236      *
   12237      * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
   12238      *
   12239      * In addition, the type variation must be one of
   12240      * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
   12241      * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
   12242      * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
   12243      * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
   12244      * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
   12245      *
   12246      * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
   12247      *
   12248      * @return true if the suggestions popup window is enabled, based on the inputType.
   12249      */
   12250     public boolean isSuggestionsEnabled() {
   12251         if (mEditor == null) return false;
   12252         if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
   12253             return false;
   12254         }
   12255         if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
   12256 
   12257         final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
   12258         return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
   12259                 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
   12260                 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
   12261                 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
   12262                 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
   12263     }
   12264 
   12265     /**
   12266      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
   12267      * selection is initiated in this View.
   12268      *
   12269      * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
   12270      * Paste, Replace and Share actions, depending on what this View supports.
   12271      *
   12272      * <p>A custom implementation can add new entries in the default menu in its
   12273      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
   12274      * method. The default actions can also be removed from the menu using
   12275      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
   12276      * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
   12277      * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
   12278      *
   12279      * <p>Returning false from
   12280      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
   12281      * will prevent the action mode from being started.
   12282      *
   12283      * <p>Action click events should be handled by the custom implementation of
   12284      * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
   12285      * android.view.MenuItem)}.
   12286      *
   12287      * <p>Note that text selection mode is not started when a TextView receives focus and the
   12288      * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
   12289      * that case, to allow for quick replacement.
   12290      */
   12291     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
   12292         createEditorIfNeeded();
   12293         mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
   12294     }
   12295 
   12296     /**
   12297      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
   12298      *
   12299      * @return The current custom selection callback.
   12300      */
   12301     public ActionMode.Callback getCustomSelectionActionModeCallback() {
   12302         return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
   12303     }
   12304 
   12305     /**
   12306      * If provided, this ActionMode.Callback will be used to create the ActionMode when text
   12307      * insertion is initiated in this View.
   12308      * The standard implementation populates the menu with a subset of Select All,
   12309      * Paste and Replace actions, depending on what this View supports.
   12310      *
   12311      * <p>A custom implementation can add new entries in the default menu in its
   12312      * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
   12313      * android.view.Menu)} method. The default actions can also be removed from the menu using
   12314      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
   12315      * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
   12316      *
   12317      * <p>Returning false from
   12318      * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
   12319      * android.view.Menu)} will prevent the action mode from being started.</p>
   12320      *
   12321      * <p>Action click events should be handled by the custom implementation of
   12322      * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
   12323      * android.view.MenuItem)}.</p>
   12324      *
   12325      * <p>Note that text insertion mode is not started when a TextView receives focus and the
   12326      * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
   12327      */
   12328     public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
   12329         createEditorIfNeeded();
   12330         mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
   12331     }
   12332 
   12333     /**
   12334      * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
   12335      *
   12336      * @return The current custom insertion callback.
   12337      */
   12338     public ActionMode.Callback getCustomInsertionActionModeCallback() {
   12339         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
   12340     }
   12341 
   12342     /**
   12343      * Sets the {@link TextClassifier} for this TextView.
   12344      */
   12345     public void setTextClassifier(@Nullable TextClassifier textClassifier) {
   12346         mTextClassifier = textClassifier;
   12347     }
   12348 
   12349     /**
   12350      * Returns the {@link TextClassifier} used by this TextView.
   12351      * If no TextClassifier has been set, this TextView uses the default set by the
   12352      * {@link TextClassificationManager}.
   12353      */
   12354     @NonNull
   12355     public TextClassifier getTextClassifier() {
   12356         if (mTextClassifier == null) {
   12357             final TextClassificationManager tcm =
   12358                     mContext.getSystemService(TextClassificationManager.class);
   12359             if (tcm != null) {
   12360                 return tcm.getTextClassifier();
   12361             }
   12362             return TextClassifier.NO_OP;
   12363         }
   12364         return mTextClassifier;
   12365     }
   12366 
   12367     /**
   12368      * Returns a session-aware text classifier.
   12369      * This method creates one if none already exists or the current one is destroyed.
   12370      */
   12371     @NonNull
   12372     TextClassifier getTextClassificationSession() {
   12373         if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) {
   12374             final TextClassificationManager tcm =
   12375                     mContext.getSystemService(TextClassificationManager.class);
   12376             if (tcm != null) {
   12377                 final String widgetType;
   12378                 if (isTextEditable()) {
   12379                     widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT;
   12380                 } else if (isTextSelectable()) {
   12381                     widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW;
   12382                 } else {
   12383                     widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW;
   12384                 }
   12385                 mTextClassificationContext = new TextClassificationContext.Builder(
   12386                         mContext.getPackageName(), widgetType)
   12387                         .build();
   12388                 if (mTextClassifier != null) {
   12389                     mTextClassificationSession = tcm.createTextClassificationSession(
   12390                             mTextClassificationContext, mTextClassifier);
   12391                 } else {
   12392                     mTextClassificationSession = tcm.createTextClassificationSession(
   12393                             mTextClassificationContext);
   12394                 }
   12395             } else {
   12396                 mTextClassificationSession = TextClassifier.NO_OP;
   12397             }
   12398         }
   12399         return mTextClassificationSession;
   12400     }
   12401 
   12402     /**
   12403      * Returns the {@link TextClassificationContext} for the current TextClassifier session.
   12404      * @see #getTextClassificationSession()
   12405      */
   12406     @Nullable
   12407     TextClassificationContext getTextClassificationContext() {
   12408         return mTextClassificationContext;
   12409     }
   12410 
   12411     /**
   12412      * Returns true if this TextView uses a no-op TextClassifier.
   12413      */
   12414     boolean usesNoOpTextClassifier() {
   12415         return getTextClassifier() == TextClassifier.NO_OP;
   12416     }
   12417 
   12418 
   12419     /**
   12420      * Starts an ActionMode for the specified TextLinkSpan.
   12421      *
   12422      * @return Whether or not we're attempting to start the action mode.
   12423      * @hide
   12424      */
   12425     public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) {
   12426         Preconditions.checkNotNull(clickedSpan);
   12427 
   12428         if (!(mText instanceof Spanned)) {
   12429             return false;
   12430         }
   12431 
   12432         final int start = ((Spanned) mText).getSpanStart(clickedSpan);
   12433         final int end = ((Spanned) mText).getSpanEnd(clickedSpan);
   12434 
   12435         if (start < 0 || end > mText.length() || start >= end) {
   12436             return false;
   12437         }
   12438 
   12439         createEditorIfNeeded();
   12440         mEditor.startLinkActionModeAsync(start, end);
   12441         return true;
   12442     }
   12443 
   12444     /**
   12445      * Handles a click on the specified TextLinkSpan.
   12446      *
   12447      * @return Whether or not the click is being handled.
   12448      * @hide
   12449      */
   12450     public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) {
   12451         Preconditions.checkNotNull(clickedSpan);
   12452         if (mText instanceof Spanned) {
   12453             final Spanned spanned = (Spanned) mText;
   12454             final int start = spanned.getSpanStart(clickedSpan);
   12455             final int end = spanned.getSpanEnd(clickedSpan);
   12456             if (start >= 0 && end <= mText.length() && start < end) {
   12457                 final TextClassification.Request request = new TextClassification.Request.Builder(
   12458                         mText, start, end)
   12459                         .setDefaultLocales(getTextLocales())
   12460                         .build();
   12461                 final Supplier<TextClassification> supplier = () ->
   12462                         getTextClassifier().classifyText(request);
   12463                 final Consumer<TextClassification> consumer = classification -> {
   12464                     if (classification != null) {
   12465                         if (!classification.getActions().isEmpty()) {
   12466                             try {
   12467                                 classification.getActions().get(0).getActionIntent().send();
   12468                             } catch (PendingIntent.CanceledException e) {
   12469                                 Log.e(LOG_TAG, "Error sending PendingIntent", e);
   12470                             }
   12471                         } else {
   12472                             Log.d(LOG_TAG, "No link action to perform");
   12473                         }
   12474                     } else {
   12475                         // classification == null
   12476                         Log.d(LOG_TAG, "Timeout while classifying text");
   12477                     }
   12478                 };
   12479                 CompletableFuture.supplyAsync(supplier)
   12480                         .completeOnTimeout(null, 1, TimeUnit.SECONDS)
   12481                         .thenAccept(consumer);
   12482                 return true;
   12483             }
   12484         }
   12485         return false;
   12486     }
   12487 
   12488     /**
   12489      * @hide
   12490      */
   12491     @UnsupportedAppUsage
   12492     protected void stopTextActionMode() {
   12493         if (mEditor != null) {
   12494             mEditor.stopTextActionMode();
   12495         }
   12496     }
   12497 
   12498     /** @hide */
   12499     public void hideFloatingToolbar(int durationMs) {
   12500         if (mEditor != null) {
   12501             mEditor.hideFloatingToolbar(durationMs);
   12502         }
   12503     }
   12504 
   12505     boolean canUndo() {
   12506         return mEditor != null && mEditor.canUndo();
   12507     }
   12508 
   12509     boolean canRedo() {
   12510         return mEditor != null && mEditor.canRedo();
   12511     }
   12512 
   12513     boolean canCut() {
   12514         if (hasPasswordTransformationMethod()) {
   12515             return false;
   12516         }
   12517 
   12518         if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
   12519                 && mEditor.mKeyListener != null) {
   12520             return true;
   12521         }
   12522 
   12523         return false;
   12524     }
   12525 
   12526     boolean canCopy() {
   12527         if (hasPasswordTransformationMethod()) {
   12528             return false;
   12529         }
   12530 
   12531         if (mText.length() > 0 && hasSelection() && mEditor != null) {
   12532             return true;
   12533         }
   12534 
   12535         return false;
   12536     }
   12537 
   12538     boolean canShare() {
   12539         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
   12540             return false;
   12541         }
   12542         return canCopy();
   12543     }
   12544 
   12545     boolean isDeviceProvisioned() {
   12546         if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
   12547             mDeviceProvisionedState = Settings.Global.getInt(
   12548                     mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
   12549                     ? DEVICE_PROVISIONED_YES
   12550                     : DEVICE_PROVISIONED_NO;
   12551         }
   12552         return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
   12553     }
   12554 
   12555     @UnsupportedAppUsage
   12556     boolean canPaste() {
   12557         return (mText instanceof Editable
   12558                 && mEditor != null && mEditor.mKeyListener != null
   12559                 && getSelectionStart() >= 0
   12560                 && getSelectionEnd() >= 0
   12561                 && getClipboardManagerForUser().hasPrimaryClip());
   12562     }
   12563 
   12564     boolean canPasteAsPlainText() {
   12565         if (!canPaste()) {
   12566             return false;
   12567         }
   12568 
   12569         final ClipData clipData = getClipboardManagerForUser().getPrimaryClip();
   12570         final ClipDescription description = clipData.getDescription();
   12571         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
   12572         final CharSequence text = clipData.getItemAt(0).getText();
   12573         if (isPlainType && (text instanceof Spanned)) {
   12574             Spanned spanned = (Spanned) text;
   12575             if (TextUtils.hasStyleSpan(spanned)) {
   12576                 return true;
   12577             }
   12578         }
   12579         return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
   12580     }
   12581 
   12582     boolean canProcessText() {
   12583         if (getId() == View.NO_ID) {
   12584             return false;
   12585         }
   12586         return canShare();
   12587     }
   12588 
   12589     boolean canSelectAllText() {
   12590         return canSelectText() && !hasPasswordTransformationMethod()
   12591                 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
   12592     }
   12593 
   12594     boolean selectAllText() {
   12595         if (mEditor != null) {
   12596             // Hide the toolbar before changing the selection to avoid flickering.
   12597             hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
   12598         }
   12599         final int length = mText.length();
   12600         Selection.setSelection(mSpannable, 0, length);
   12601         return length > 0;
   12602     }
   12603 
   12604     void replaceSelectionWithText(CharSequence text) {
   12605         ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
   12606     }
   12607 
   12608     /**
   12609      * Paste clipboard content between min and max positions.
   12610      */
   12611     private void paste(int min, int max, boolean withFormatting) {
   12612         ClipboardManager clipboard = getClipboardManagerForUser();
   12613         ClipData clip = clipboard.getPrimaryClip();
   12614         if (clip != null) {
   12615             boolean didFirst = false;
   12616             for (int i = 0; i < clip.getItemCount(); i++) {
   12617                 final CharSequence paste;
   12618                 if (withFormatting) {
   12619                     paste = clip.getItemAt(i).coerceToStyledText(getContext());
   12620                 } else {
   12621                     // Get an item as text and remove all spans by toString().
   12622                     final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
   12623                     paste = (text instanceof Spanned) ? text.toString() : text;
   12624                 }
   12625                 if (paste != null) {
   12626                     if (!didFirst) {
   12627                         Selection.setSelection(mSpannable, max);
   12628                         ((Editable) mText).replace(min, max, paste);
   12629                         didFirst = true;
   12630                     } else {
   12631                         ((Editable) mText).insert(getSelectionEnd(), "\n");
   12632                         ((Editable) mText).insert(getSelectionEnd(), paste);
   12633                     }
   12634                 }
   12635             }
   12636             sLastCutCopyOrTextChangedTime = 0;
   12637         }
   12638     }
   12639 
   12640     private void shareSelectedText() {
   12641         String selectedText = getSelectedText();
   12642         if (selectedText != null && !selectedText.isEmpty()) {
   12643             Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
   12644             sharingIntent.setType("text/plain");
   12645             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
   12646             selectedText = TextUtils.trimToParcelableSize(selectedText);
   12647             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
   12648             getContext().startActivity(Intent.createChooser(sharingIntent, null));
   12649             Selection.setSelection(mSpannable, getSelectionEnd());
   12650         }
   12651     }
   12652 
   12653     @CheckResult
   12654     private boolean setPrimaryClip(ClipData clip) {
   12655         ClipboardManager clipboard = getClipboardManagerForUser();
   12656         try {
   12657             clipboard.setPrimaryClip(clip);
   12658         } catch (Throwable t) {
   12659             return false;
   12660         }
   12661         sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
   12662         return true;
   12663     }
   12664 
   12665     /**
   12666      * Get the character offset closest to the specified absolute position. A typical use case is to
   12667      * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
   12668      *
   12669      * @param x The horizontal absolute position of a point on screen
   12670      * @param y The vertical absolute position of a point on screen
   12671      * @return the character offset for the character whose position is closest to the specified
   12672      *  position. Returns -1 if there is no layout.
   12673      */
   12674     public int getOffsetForPosition(float x, float y) {
   12675         if (getLayout() == null) return -1;
   12676         final int line = getLineAtCoordinate(y);
   12677         final int offset = getOffsetAtCoordinate(line, x);
   12678         return offset;
   12679     }
   12680 
   12681     float convertToLocalHorizontalCoordinate(float x) {
   12682         x -= getTotalPaddingLeft();
   12683         // Clamp the position to inside of the view.
   12684         x = Math.max(0.0f, x);
   12685         x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
   12686         x += getScrollX();
   12687         return x;
   12688     }
   12689 
   12690     @UnsupportedAppUsage
   12691     int getLineAtCoordinate(float y) {
   12692         y -= getTotalPaddingTop();
   12693         // Clamp the position to inside of the view.
   12694         y = Math.max(0.0f, y);
   12695         y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
   12696         y += getScrollY();
   12697         return getLayout().getLineForVertical((int) y);
   12698     }
   12699 
   12700     int getLineAtCoordinateUnclamped(float y) {
   12701         y -= getTotalPaddingTop();
   12702         y += getScrollY();
   12703         return getLayout().getLineForVertical((int) y);
   12704     }
   12705 
   12706     int getOffsetAtCoordinate(int line, float x) {
   12707         x = convertToLocalHorizontalCoordinate(x);
   12708         return getLayout().getOffsetForHorizontal(line, x);
   12709     }
   12710 
   12711     @Override
   12712     public boolean onDragEvent(DragEvent event) {
   12713         switch (event.getAction()) {
   12714             case DragEvent.ACTION_DRAG_STARTED:
   12715                 return mEditor != null && mEditor.hasInsertionController();
   12716 
   12717             case DragEvent.ACTION_DRAG_ENTERED:
   12718                 TextView.this.requestFocus();
   12719                 return true;
   12720 
   12721             case DragEvent.ACTION_DRAG_LOCATION:
   12722                 if (mText instanceof Spannable) {
   12723                     final int offset = getOffsetForPosition(event.getX(), event.getY());
   12724                     Selection.setSelection(mSpannable, offset);
   12725                 }
   12726                 return true;
   12727 
   12728             case DragEvent.ACTION_DROP:
   12729                 if (mEditor != null) mEditor.onDrop(event);
   12730                 return true;
   12731 
   12732             case DragEvent.ACTION_DRAG_ENDED:
   12733             case DragEvent.ACTION_DRAG_EXITED:
   12734             default:
   12735                 return true;
   12736         }
   12737     }
   12738 
   12739     boolean isInBatchEditMode() {
   12740         if (mEditor == null) return false;
   12741         final Editor.InputMethodState ims = mEditor.mInputMethodState;
   12742         if (ims != null) {
   12743             return ims.mBatchEditNesting > 0;
   12744         }
   12745         return mEditor.mInBatchEditControllers;
   12746     }
   12747 
   12748     @Override
   12749     public void onRtlPropertiesChanged(int layoutDirection) {
   12750         super.onRtlPropertiesChanged(layoutDirection);
   12751 
   12752         final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
   12753         if (mTextDir != newTextDir) {
   12754             mTextDir = newTextDir;
   12755             if (mLayout != null) {
   12756                 checkForRelayout();
   12757             }
   12758         }
   12759     }
   12760 
   12761     /**
   12762      * Returns resolved {@link TextDirectionHeuristic} that will be used for text layout.
   12763      * The {@link TextDirectionHeuristic} that is used by TextView is only available after
   12764      * {@link #getTextDirection()} and {@link #getLayoutDirection()} is resolved. Therefore the
   12765      * return value may not be the same as the one TextView uses if the View's layout direction is
   12766      * not resolved or detached from parent root view.
   12767      */
   12768     public @NonNull TextDirectionHeuristic getTextDirectionHeuristic() {
   12769         if (hasPasswordTransformationMethod()) {
   12770             // passwords fields should be LTR
   12771             return TextDirectionHeuristics.LTR;
   12772         }
   12773 
   12774         if (mEditor != null
   12775                 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
   12776                     == EditorInfo.TYPE_CLASS_PHONE) {
   12777             // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
   12778             // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
   12779             // RTL digits.
   12780             final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
   12781             final String zero = symbols.getDigitStrings()[0];
   12782             // In case the zero digit is multi-codepoint, just use the first codepoint to determine
   12783             // direction.
   12784             final int firstCodepoint = zero.codePointAt(0);
   12785             final byte digitDirection = Character.getDirectionality(firstCodepoint);
   12786             if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
   12787                     || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
   12788                 return TextDirectionHeuristics.RTL;
   12789             } else {
   12790                 return TextDirectionHeuristics.LTR;
   12791             }
   12792         }
   12793 
   12794         // Always need to resolve layout direction first
   12795         final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
   12796 
   12797         // Now, we can select the heuristic
   12798         switch (getTextDirection()) {
   12799             default:
   12800             case TEXT_DIRECTION_FIRST_STRONG:
   12801                 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
   12802                         TextDirectionHeuristics.FIRSTSTRONG_LTR);
   12803             case TEXT_DIRECTION_ANY_RTL:
   12804                 return TextDirectionHeuristics.ANYRTL_LTR;
   12805             case TEXT_DIRECTION_LTR:
   12806                 return TextDirectionHeuristics.LTR;
   12807             case TEXT_DIRECTION_RTL:
   12808                 return TextDirectionHeuristics.RTL;
   12809             case TEXT_DIRECTION_LOCALE:
   12810                 return TextDirectionHeuristics.LOCALE;
   12811             case TEXT_DIRECTION_FIRST_STRONG_LTR:
   12812                 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
   12813             case TEXT_DIRECTION_FIRST_STRONG_RTL:
   12814                 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
   12815         }
   12816     }
   12817 
   12818     /**
   12819      * @hide
   12820      */
   12821     @Override
   12822     public void onResolveDrawables(int layoutDirection) {
   12823         // No need to resolve twice
   12824         if (mLastLayoutDirection == layoutDirection) {
   12825             return;
   12826         }
   12827         mLastLayoutDirection = layoutDirection;
   12828 
   12829         // Resolve drawables
   12830         if (mDrawables != null) {
   12831             if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
   12832                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
   12833                 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
   12834                 applyCompoundDrawableTint();
   12835             }
   12836         }
   12837     }
   12838 
   12839     /**
   12840      * Prepares a drawable for display by propagating layout direction and
   12841      * drawable state.
   12842      *
   12843      * @param dr the drawable to prepare
   12844      */
   12845     private void prepareDrawableForDisplay(@Nullable Drawable dr) {
   12846         if (dr == null) {
   12847             return;
   12848         }
   12849 
   12850         dr.setLayoutDirection(getLayoutDirection());
   12851 
   12852         if (dr.isStateful()) {
   12853             dr.setState(getDrawableState());
   12854             dr.jumpToCurrentState();
   12855         }
   12856     }
   12857 
   12858     /**
   12859      * @hide
   12860      */
   12861     protected void resetResolvedDrawables() {
   12862         super.resetResolvedDrawables();
   12863         mLastLayoutDirection = -1;
   12864     }
   12865 
   12866     /**
   12867      * @hide
   12868      */
   12869     protected void viewClicked(InputMethodManager imm) {
   12870         if (imm != null) {
   12871             imm.viewClicked(this);
   12872         }
   12873     }
   12874 
   12875     /**
   12876      * Deletes the range of text [start, end[.
   12877      * @hide
   12878      */
   12879     @UnsupportedAppUsage
   12880     protected void deleteText_internal(int start, int end) {
   12881         ((Editable) mText).delete(start, end);
   12882     }
   12883 
   12884     /**
   12885      * Replaces the range of text [start, end[ by replacement text
   12886      * @hide
   12887      */
   12888     protected void replaceText_internal(int start, int end, CharSequence text) {
   12889         ((Editable) mText).replace(start, end, text);
   12890     }
   12891 
   12892     /**
   12893      * Sets a span on the specified range of text
   12894      * @hide
   12895      */
   12896     protected void setSpan_internal(Object span, int start, int end, int flags) {
   12897         ((Editable) mText).setSpan(span, start, end, flags);
   12898     }
   12899 
   12900     /**
   12901      * Moves the cursor to the specified offset position in text
   12902      * @hide
   12903      */
   12904     protected void setCursorPosition_internal(int start, int end) {
   12905         Selection.setSelection(((Editable) mText), start, end);
   12906     }
   12907 
   12908     /**
   12909      * An Editor should be created as soon as any of the editable-specific fields (grouped
   12910      * inside the Editor object) is assigned to a non-default value.
   12911      * This method will create the Editor if needed.
   12912      *
   12913      * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
   12914      * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
   12915      * Editor for backward compatibility, as soon as one of these fields is assigned.
   12916      *
   12917      * Also note that for performance reasons, the mEditor is created when needed, but not
   12918      * reset when no more edit-specific fields are needed.
   12919      */
   12920     @UnsupportedAppUsage
   12921     private void createEditorIfNeeded() {
   12922         if (mEditor == null) {
   12923             mEditor = new Editor(this);
   12924         }
   12925     }
   12926 
   12927     /**
   12928      * @hide
   12929      */
   12930     @Override
   12931     @UnsupportedAppUsage
   12932     public CharSequence getIterableTextForAccessibility() {
   12933         return mText;
   12934     }
   12935 
   12936     private void ensureIterableTextForAccessibilitySelectable() {
   12937         if (!(mText instanceof Spannable)) {
   12938             setText(mText, BufferType.SPANNABLE);
   12939         }
   12940     }
   12941 
   12942     /**
   12943      * @hide
   12944      */
   12945     @Override
   12946     public TextSegmentIterator getIteratorForGranularity(int granularity) {
   12947         switch (granularity) {
   12948             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
   12949                 Spannable text = (Spannable) getIterableTextForAccessibility();
   12950                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
   12951                     AccessibilityIterators.LineTextSegmentIterator iterator =
   12952                             AccessibilityIterators.LineTextSegmentIterator.getInstance();
   12953                     iterator.initialize(text, getLayout());
   12954                     return iterator;
   12955                 }
   12956             } break;
   12957             case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
   12958                 Spannable text = (Spannable) getIterableTextForAccessibility();
   12959                 if (!TextUtils.isEmpty(text) && getLayout() != null) {
   12960                     AccessibilityIterators.PageTextSegmentIterator iterator =
   12961                             AccessibilityIterators.PageTextSegmentIterator.getInstance();
   12962                     iterator.initialize(this);
   12963                     return iterator;
   12964                 }
   12965             } break;
   12966         }
   12967         return super.getIteratorForGranularity(granularity);
   12968     }
   12969 
   12970     /**
   12971      * @hide
   12972      */
   12973     @Override
   12974     public int getAccessibilitySelectionStart() {
   12975         return getSelectionStart();
   12976     }
   12977 
   12978     /**
   12979      * @hide
   12980      */
   12981     public boolean isAccessibilitySelectionExtendable() {
   12982         return true;
   12983     }
   12984 
   12985     /**
   12986      * @hide
   12987      */
   12988     @Override
   12989     public int getAccessibilitySelectionEnd() {
   12990         return getSelectionEnd();
   12991     }
   12992 
   12993     /**
   12994      * @hide
   12995      */
   12996     @Override
   12997     public void setAccessibilitySelection(int start, int end) {
   12998         if (getAccessibilitySelectionStart() == start
   12999                 && getAccessibilitySelectionEnd() == end) {
   13000             return;
   13001         }
   13002         CharSequence text = getIterableTextForAccessibility();
   13003         if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
   13004             Selection.setSelection((Spannable) text, start, end);
   13005         } else {
   13006             Selection.removeSelection((Spannable) text);
   13007         }
   13008         // Hide all selection controllers used for adjusting selection
   13009         // since we are doing so explicitlty by other means and these
   13010         // controllers interact with how selection behaves.
   13011         if (mEditor != null) {
   13012             mEditor.hideCursorAndSpanControllers();
   13013             mEditor.stopTextActionMode();
   13014         }
   13015     }
   13016 
   13017     /** @hide */
   13018     @Override
   13019     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
   13020         super.encodeProperties(stream);
   13021 
   13022         TruncateAt ellipsize = getEllipsize();
   13023         stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
   13024         stream.addProperty("text:textSize", getTextSize());
   13025         stream.addProperty("text:scaledTextSize", getScaledTextSize());
   13026         stream.addProperty("text:typefaceStyle", getTypefaceStyle());
   13027         stream.addProperty("text:selectionStart", getSelectionStart());
   13028         stream.addProperty("text:selectionEnd", getSelectionEnd());
   13029         stream.addProperty("text:curTextColor", mCurTextColor);
   13030         stream.addProperty("text:text", mText == null ? null : mText.toString());
   13031         stream.addProperty("text:gravity", mGravity);
   13032     }
   13033 
   13034     /**
   13035      * User interface state that is stored by TextView for implementing
   13036      * {@link View#onSaveInstanceState}.
   13037      */
   13038     public static class SavedState extends BaseSavedState {
   13039         int selStart = -1;
   13040         int selEnd = -1;
   13041         @UnsupportedAppUsage
   13042         CharSequence text;
   13043         boolean frozenWithFocus;
   13044         CharSequence error;
   13045         ParcelableParcel editorState;  // Optional state from Editor.
   13046 
   13047         SavedState(Parcelable superState) {
   13048             super(superState);
   13049         }
   13050 
   13051         @Override
   13052         public void writeToParcel(Parcel out, int flags) {
   13053             super.writeToParcel(out, flags);
   13054             out.writeInt(selStart);
   13055             out.writeInt(selEnd);
   13056             out.writeInt(frozenWithFocus ? 1 : 0);
   13057             TextUtils.writeToParcel(text, out, flags);
   13058 
   13059             if (error == null) {
   13060                 out.writeInt(0);
   13061             } else {
   13062                 out.writeInt(1);
   13063                 TextUtils.writeToParcel(error, out, flags);
   13064             }
   13065 
   13066             if (editorState == null) {
   13067                 out.writeInt(0);
   13068             } else {
   13069                 out.writeInt(1);
   13070                 editorState.writeToParcel(out, flags);
   13071             }
   13072         }
   13073 
   13074         @Override
   13075         public String toString() {
   13076             String str = "TextView.SavedState{"
   13077                     + Integer.toHexString(System.identityHashCode(this))
   13078                     + " start=" + selStart + " end=" + selEnd;
   13079             if (text != null) {
   13080                 str += " text=" + text;
   13081             }
   13082             return str + "}";
   13083         }
   13084 
   13085         @SuppressWarnings("hiding")
   13086         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR =
   13087                 new Parcelable.Creator<SavedState>() {
   13088                     public SavedState createFromParcel(Parcel in) {
   13089                         return new SavedState(in);
   13090                     }
   13091 
   13092                     public SavedState[] newArray(int size) {
   13093                         return new SavedState[size];
   13094                     }
   13095                 };
   13096 
   13097         private SavedState(Parcel in) {
   13098             super(in);
   13099             selStart = in.readInt();
   13100             selEnd = in.readInt();
   13101             frozenWithFocus = (in.readInt() != 0);
   13102             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   13103 
   13104             if (in.readInt() != 0) {
   13105                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   13106             }
   13107 
   13108             if (in.readInt() != 0) {
   13109                 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
   13110             }
   13111         }
   13112     }
   13113 
   13114     private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
   13115         private char[] mChars;
   13116         private int mStart, mLength;
   13117 
   13118         public CharWrapper(char[] chars, int start, int len) {
   13119             mChars = chars;
   13120             mStart = start;
   13121             mLength = len;
   13122         }
   13123 
   13124         /* package */ void set(char[] chars, int start, int len) {
   13125             mChars = chars;
   13126             mStart = start;
   13127             mLength = len;
   13128         }
   13129 
   13130         public int length() {
   13131             return mLength;
   13132         }
   13133 
   13134         public char charAt(int off) {
   13135             return mChars[off + mStart];
   13136         }
   13137 
   13138         @Override
   13139         public String toString() {
   13140             return new String(mChars, mStart, mLength);
   13141         }
   13142 
   13143         public CharSequence subSequence(int start, int end) {
   13144             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   13145                 throw new IndexOutOfBoundsException(start + ", " + end);
   13146             }
   13147 
   13148             return new String(mChars, start + mStart, end - start);
   13149         }
   13150 
   13151         public void getChars(int start, int end, char[] buf, int off) {
   13152             if (start < 0 || end < 0 || start > mLength || end > mLength) {
   13153                 throw new IndexOutOfBoundsException(start + ", " + end);
   13154             }
   13155 
   13156             System.arraycopy(mChars, start + mStart, buf, off, end - start);
   13157         }
   13158 
   13159         @Override
   13160         public void drawText(BaseCanvas c, int start, int end,
   13161                              float x, float y, Paint p) {
   13162             c.drawText(mChars, start + mStart, end - start, x, y, p);
   13163         }
   13164 
   13165         @Override
   13166         public void drawTextRun(BaseCanvas c, int start, int end,
   13167                 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
   13168             int count = end - start;
   13169             int contextCount = contextEnd - contextStart;
   13170             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
   13171                     contextCount, x, y, isRtl, p);
   13172         }
   13173 
   13174         public float measureText(int start, int end, Paint p) {
   13175             return p.measureText(mChars, start + mStart, end - start);
   13176         }
   13177 
   13178         public int getTextWidths(int start, int end, float[] widths, Paint p) {
   13179             return p.getTextWidths(mChars, start + mStart, end - start, widths);
   13180         }
   13181 
   13182         public float getTextRunAdvances(int start, int end, int contextStart,
   13183                 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
   13184                 Paint p) {
   13185             int count = end - start;
   13186             int contextCount = contextEnd - contextStart;
   13187             return p.getTextRunAdvances(mChars, start + mStart, count,
   13188                     contextStart + mStart, contextCount, isRtl, advances,
   13189                     advancesIndex);
   13190         }
   13191 
   13192         public int getTextRunCursor(int contextStart, int contextEnd, boolean isRtl,
   13193                 int offset, int cursorOpt, Paint p) {
   13194             int contextCount = contextEnd - contextStart;
   13195             return p.getTextRunCursor(mChars, contextStart + mStart,
   13196                     contextCount, isRtl, offset + mStart, cursorOpt);
   13197         }
   13198     }
   13199 
   13200     private static final class Marquee {
   13201         // TODO: Add an option to configure this
   13202         private static final float MARQUEE_DELTA_MAX = 0.07f;
   13203         private static final int MARQUEE_DELAY = 1200;
   13204         private static final int MARQUEE_DP_PER_SECOND = 30;
   13205 
   13206         private static final byte MARQUEE_STOPPED = 0x0;
   13207         private static final byte MARQUEE_STARTING = 0x1;
   13208         private static final byte MARQUEE_RUNNING = 0x2;
   13209 
   13210         private final WeakReference<TextView> mView;
   13211         private final Choreographer mChoreographer;
   13212 
   13213         private byte mStatus = MARQUEE_STOPPED;
   13214         private final float mPixelsPerMs;
   13215         private float mMaxScroll;
   13216         private float mMaxFadeScroll;
   13217         private float mGhostStart;
   13218         private float mGhostOffset;
   13219         private float mFadeStop;
   13220         private int mRepeatLimit;
   13221 
   13222         private float mScroll;
   13223         private long mLastAnimationMs;
   13224 
   13225         Marquee(TextView v) {
   13226             final float density = v.getContext().getResources().getDisplayMetrics().density;
   13227             mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
   13228             mView = new WeakReference<TextView>(v);
   13229             mChoreographer = Choreographer.getInstance();
   13230         }
   13231 
   13232         private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
   13233             @Override
   13234             public void doFrame(long frameTimeNanos) {
   13235                 tick();
   13236             }
   13237         };
   13238 
   13239         private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
   13240             @Override
   13241             public void doFrame(long frameTimeNanos) {
   13242                 mStatus = MARQUEE_RUNNING;
   13243                 mLastAnimationMs = mChoreographer.getFrameTime();
   13244                 tick();
   13245             }
   13246         };
   13247 
   13248         private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
   13249             @Override
   13250             public void doFrame(long frameTimeNanos) {
   13251                 if (mStatus == MARQUEE_RUNNING) {
   13252                     if (mRepeatLimit >= 0) {
   13253                         mRepeatLimit--;
   13254                     }
   13255                     start(mRepeatLimit);
   13256                 }
   13257             }
   13258         };
   13259 
   13260         void tick() {
   13261             if (mStatus != MARQUEE_RUNNING) {
   13262                 return;
   13263             }
   13264 
   13265             mChoreographer.removeFrameCallback(mTickCallback);
   13266 
   13267             final TextView textView = mView.get();
   13268             if (textView != null && (textView.isFocused() || textView.isSelected())) {
   13269                 long currentMs = mChoreographer.getFrameTime();
   13270                 long deltaMs = currentMs - mLastAnimationMs;
   13271                 mLastAnimationMs = currentMs;
   13272                 float deltaPx = deltaMs * mPixelsPerMs;
   13273                 mScroll += deltaPx;
   13274                 if (mScroll > mMaxScroll) {
   13275                     mScroll = mMaxScroll;
   13276                     mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
   13277                 } else {
   13278                     mChoreographer.postFrameCallback(mTickCallback);
   13279                 }
   13280                 textView.invalidate();
   13281             }
   13282         }
   13283 
   13284         void stop() {
   13285             mStatus = MARQUEE_STOPPED;
   13286             mChoreographer.removeFrameCallback(mStartCallback);
   13287             mChoreographer.removeFrameCallback(mRestartCallback);
   13288             mChoreographer.removeFrameCallback(mTickCallback);
   13289             resetScroll();
   13290         }
   13291 
   13292         private void resetScroll() {
   13293             mScroll = 0.0f;
   13294             final TextView textView = mView.get();
   13295             if (textView != null) textView.invalidate();
   13296         }
   13297 
   13298         void start(int repeatLimit) {
   13299             if (repeatLimit == 0) {
   13300                 stop();
   13301                 return;
   13302             }
   13303             mRepeatLimit = repeatLimit;
   13304             final TextView textView = mView.get();
   13305             if (textView != null && textView.mLayout != null) {
   13306                 mStatus = MARQUEE_STARTING;
   13307                 mScroll = 0.0f;
   13308                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
   13309                         - textView.getCompoundPaddingRight();
   13310                 final float lineWidth = textView.mLayout.getLineWidth(0);
   13311                 final float gap = textWidth / 3.0f;
   13312                 mGhostStart = lineWidth - textWidth + gap;
   13313                 mMaxScroll = mGhostStart + textWidth;
   13314                 mGhostOffset = lineWidth + gap;
   13315                 mFadeStop = lineWidth + textWidth / 6.0f;
   13316                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
   13317 
   13318                 textView.invalidate();
   13319                 mChoreographer.postFrameCallback(mStartCallback);
   13320             }
   13321         }
   13322 
   13323         float getGhostOffset() {
   13324             return mGhostOffset;
   13325         }
   13326 
   13327         float getScroll() {
   13328             return mScroll;
   13329         }
   13330 
   13331         float getMaxFadeScroll() {
   13332             return mMaxFadeScroll;
   13333         }
   13334 
   13335         boolean shouldDrawLeftFade() {
   13336             return mScroll <= mFadeStop;
   13337         }
   13338 
   13339         boolean shouldDrawGhost() {
   13340             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
   13341         }
   13342 
   13343         boolean isRunning() {
   13344             return mStatus == MARQUEE_RUNNING;
   13345         }
   13346 
   13347         boolean isStopped() {
   13348             return mStatus == MARQUEE_STOPPED;
   13349         }
   13350     }
   13351 
   13352     private class ChangeWatcher implements TextWatcher, SpanWatcher {
   13353 
   13354         private CharSequence mBeforeText;
   13355 
   13356         public void beforeTextChanged(CharSequence buffer, int start,
   13357                                       int before, int after) {
   13358             if (DEBUG_EXTRACT) {
   13359                 Log.v(LOG_TAG, "beforeTextChanged start=" + start
   13360                         + " before=" + before + " after=" + after + ": " + buffer);
   13361             }
   13362 
   13363             if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) {
   13364                 mBeforeText = mTransformed.toString();
   13365             }
   13366 
   13367             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
   13368         }
   13369 
   13370         public void onTextChanged(CharSequence buffer, int start, int before, int after) {
   13371             if (DEBUG_EXTRACT) {
   13372                 Log.v(LOG_TAG, "onTextChanged start=" + start
   13373                         + " before=" + before + " after=" + after + ": " + buffer);
   13374             }
   13375             TextView.this.handleTextChanged(buffer, start, before, after);
   13376 
   13377             if (AccessibilityManager.getInstance(mContext).isEnabled()
   13378                     && (isFocused() || isSelected() && isShown())) {
   13379                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
   13380                 mBeforeText = null;
   13381             }
   13382         }
   13383 
   13384         public void afterTextChanged(Editable buffer) {
   13385             if (DEBUG_EXTRACT) {
   13386                 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
   13387             }
   13388             TextView.this.sendAfterTextChanged(buffer);
   13389 
   13390             if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
   13391                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
   13392             }
   13393         }
   13394 
   13395         public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
   13396             if (DEBUG_EXTRACT) {
   13397                 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
   13398                         + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
   13399             }
   13400             TextView.this.spanChange(buf, what, s, st, e, en);
   13401         }
   13402 
   13403         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
   13404             if (DEBUG_EXTRACT) {
   13405                 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
   13406             }
   13407             TextView.this.spanChange(buf, what, -1, s, -1, e);
   13408         }
   13409 
   13410         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
   13411             if (DEBUG_EXTRACT) {
   13412                 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
   13413             }
   13414             TextView.this.spanChange(buf, what, s, -1, e, -1);
   13415         }
   13416     }
   13417 }
   13418