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