/*
 * Copyright 2015 - 2019 Michael Rapp
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package de.mrapp.android.validation;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.text.method.KeyListener;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.method.TransformationMethod;
import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.widget.TextView.BufferType;
import android.widget.TextView.OnEditorActionListener;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Locale;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.mrapp.util.Condition;

/**
 * A view, which allows to enter text. The text may be validated according to the pattern, which is
 * suggested by the Material Design guidelines.
 *
 * @author Michael Rapp
 * @since 1.0.0
 */
@SuppressWarnings("unused")
public class EditText extends AbstractValidateableView<android.widget.EditText, CharSequence> {

    /**
     * A data structure, which allows to save the internal state of an {@link EditText}.
     */
    public static class SavedState extends BaseSavedState {

        /**
         * A creator, which allows to create instances of the class {@link EditText} from parcels.
         */
        public static final Parcelable.Creator<SavedState> CREATOR =
                new Parcelable.Creator<SavedState>() {

                    @Override
                    public SavedState createFromParcel(final Parcel in) {
                        return new SavedState(in);
                    }

                    @Override
                    public SavedState[] newArray(final int size) {
                        return new SavedState[size];
                    }

                };

        /**
         * The internal state of the edit text.
         */
        Parcelable viewState;

        /**
         * The maximum number of characters, the edit text is allowed to contain.
         */
        int maxNumberOfCharacters;

        /**
         * Creates a new data structure, which allows to store the internal state of an {@link
         * EditText}. This constructor is used when reading from a parcel. It reads the state of the
         * superclass.
         *
         * @param source
         *         The parcel to read read from as a instance of the class {@link Parcel}. The
         *         parcel may not be null
         */
        private SavedState(@NonNull final Parcel source) {
            super(source);
            ClassLoader classLoader = Parcelable.class.getClassLoader();
            viewState = source.readParcelable(classLoader);
            maxNumberOfCharacters = source.readInt();
        }

        /**
         * Creates a new data structure, which allows to store the internal state of an {@link
         * EditText}. This constructor is called by derived classes when saving their states.
         *
         * @param superState
         *         The state of the superclass of this view, as an instance of the type {@link
         *         Parcelable}. The state may not be null
         */
        SavedState(@NonNull final Parcelable superState) {
            super(superState);
        }

        @Override
        public final void writeToParcel(final Parcel destination, final int flags) {
            super.writeToParcel(destination, flags);
            destination.writeParcelable(viewState, flags);
            destination.writeInt(maxNumberOfCharacters);
        }

    }

    /**
     * The maximum number of characters, the edit should be allowed to contain by default.
     */
    private static final int DEFAULT_MAX_NUMBER_OF_CHARACTERS = -1;

    /**
     * The value, which corresponds to the enum value <code>TruncateAt.START</code>.
     */
    private static final int ELLIPSIZE_START_VALUE = 1;

    /**
     * The value, which corresponds to the enum value <code>TruncateAt.MIDDLE</code>.
     */
    private static final int ELLIPSIZE_MIDDLE_VALUE = 2;

    /**
     * The value, which corresponds to the enum value <code>TruncateAt.END</code>.
     */
    private static final int ELLIPSIZE_END_VALUE = 3;

    /**
     * The value, which corresponds to the enum value <code>TruncateAt.MARQUEE</code>.
     */
    private static final int ELLIPSIZE_MARQUEE_VALUE = 4;

    /**
     * The value, which corresponds to the enum value <code>Typeface.SANS_SERIF</code>.
     */
    private static final int TYPEFACE_SANS_SERIF_VALUE = 1;

    /**
     * The value, which corresponds to the enum value <code>Typeface.SERIF</code>.
     */
    private static final int TYPEFACE_SERIF_VALUE = 2;

    /**
     * The value, which corresponds to the enum value <code>Typeface.MONOSPACE</code>.
     */
    private static final int TYPEFACE_MONOSPACE_VALUE = 3;

    /**
     * The maximum number of characters, the edit text is allowed to contain.
     */
    private int maxNumberOfCharacters;

    /**
     * Initializes the view.
     *
     * @param attributeSet
     *         The attribute set, the attributes should be obtained from, as an instance of the type
     *         {@link AttributeSet} or null, if no attributes should be obtained
     */
    private void initialize(@Nullable final AttributeSet attributeSet) {
        obtainStyledAttributes(attributeSet);
        getView().addTextChangedListener(createTextChangeListener());
    }

    /**
     * Obtains all attributes from a specific attribute set.
     *
     * @param attributeSet
     *         The attribute set, the attributes should be obtained from, as an instance of the type
     *         {@link AttributeSet} or null, if no attributes should be obtained
     */
    private void obtainStyledAttributes(@Nullable final AttributeSet attributeSet) {
        TypedArray typedArray =
                getContext().obtainStyledAttributes(attributeSet, R.styleable.EditText);
        try {
            obtainMaxNumberOfCharacters(typedArray);
            obtainEditTextStyledAttributes(typedArray);
        } finally {
            typedArray.recycle();
        }
    }

    /**
     * Obtains the maximum number of characters, the edit text should be allowed to contain, from a
     * specific typed array.
     *
     * @param typedArray
     *         The typed array, the maximum number of characters, the edit text should be allowed to
     *         contain, should be obtained from, as an instance of the class {@link TypedArray}. The
     *         typed array may not be null
     */
    private void obtainMaxNumberOfCharacters(@NonNull final TypedArray typedArray) {
        setMaxNumberOfCharacters(typedArray.getInt(R.styleable.EditText_maxNumberOfCharacters,
                DEFAULT_MAX_NUMBER_OF_CHARACTERS));
    }

    /**
     * Obtains all attributes, which are defined by an {@link android.widget.EditText} widget, from
     * a specific typed array.
     *
     * @param typedArray
     *         The typed array, the attributes should be obtained from, as an instance of the class
     *         {@link TypedArray}. The typed array may not be null
     */
    private void obtainEditTextStyledAttributes(@NonNull final TypedArray typedArray) {
        Drawable drawableLeft = null;
        Drawable drawableTop = null;
        Drawable drawableRight = null;
        Drawable drawableBottom = null;
        CharSequence imeActionLabel = null;
        int imeActionId = getImeActionId();
        float lineSpacingExtra = -1;
        float lineSpacingMultiplier = -1;
        int shadowColor = 0;
        float shadowDx = 0;
        float shadowDy = 0;
        float shadowRadius = 0;

        for (int i = 0; i < typedArray.getIndexCount(); i++) {
            int index = typedArray.getIndex(i);

            if (index == R.styleable.EditText_android_autoLink) {
                setAutoLinkMask(typedArray.getInt(index, getAutoLinkMask()));
            } else if (index == R.styleable.EditText_android_cursorVisible) {
                setCursorVisible(typedArray.getBoolean(index, isCursorVisible()));
            } else if (index == R.styleable.EditText_android_drawableBottom) {
                drawableBottom = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_drawableEnd) {
                drawableRight = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_drawableLeft) {
                drawableLeft = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_drawablePadding) {
                setCompoundDrawablePadding(
                        typedArray.getDimensionPixelSize(index, getCompoundDrawablePadding()));
            } else if (index == R.styleable.EditText_android_drawableRight) {
                drawableRight = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_drawableStart) {
                drawableLeft = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_drawableTop) {
                drawableTop = typedArray.getDrawable(index);
            } else if (index == R.styleable.EditText_android_elegantTextHeight) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    setElegantTextHeight(typedArray.getBoolean(index, false));
                }
            } else if (index == R.styleable.EditText_android_ellipsize) {
                int ellipsize = typedArray.getInt(index, -1);

                if (ellipsize != -1) {
                    setEllipsize(parseEllipsize(ellipsize));
                }
            } else if (index == R.styleable.EditText_android_ems) {
                setEms(typedArray.getInt(index, -1));
            } else if (index == R.styleable.EditText_android_fontFeatureSettings) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    setFontFeatureSettings(typedArray.getString(index));
                }
            } else if (index == R.styleable.EditText_android_freezesText) {
                setFreezesText(typedArray.getBoolean(index, getFreezesText()));
            } else if (index == R.styleable.EditText_android_hint) {
                setHint(typedArray.getText(index));
            } else if (index == R.styleable.EditText_android_imeActionId) {
                imeActionId = typedArray.getInt(index, getImeActionId());
            } else if (index == R.styleable.EditText_android_imeActionLabel) {
                imeActionLabel = typedArray.getText(index);
            } else if (index == R.styleable.EditText_android_imeOptions) {
                setImeOptions(typedArray.getInt(index, getImeOptions()));
            } else if (index == R.styleable.EditText_android_includeFontPadding) {
                setIncludeFontPadding(typedArray.getBoolean(index, getIncludeFontPadding()));
            } else if (index == R.styleable.EditText_android_inputType) {
                setInputType(typedArray.getInt(index, getInputType()));
            } else if (index == R.styleable.EditText_android_letterSpacing) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    setLetterSpacing(typedArray.getFloat(index, getLetterSpacing()));
                }
            } else if (index == R.styleable.EditText_android_lines) {
                setLines(typedArray.getInt(index, -1));
            } else if (index == R.styleable.EditText_android_lineSpacingExtra) {
                lineSpacingExtra = typedArray.getDimensionPixelSize(index, -1);
            } else if (index == R.styleable.EditText_android_lineSpacingMultiplier) {
                lineSpacingMultiplier = typedArray.getFloat(index, -1);
            } else if (index == R.styleable.EditText_android_linksClickable) {
                setLinksClickable(typedArray.getBoolean(index, getLinksClickable()));
            } else if (index == R.styleable.EditText_android_marqueeRepeatLimit) {
                setMarqueeRepeatLimit(typedArray.getInt(index, getMarqueeRepeatLimit()));
            } else if (index == R.styleable.EditText_android_maxEms) {
                setMaxEms(typedArray.getInt(index, getMaxEms()));
            } else if (index == R.styleable.EditText_android_maxLength) {
                int maxLength = typedArray.getInt(index, -1);

                if (maxLength >= 0) {
                    setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
                } else {
                    setFilters(new InputFilter[0]);
                }
            } else if (index == R.styleable.EditText_android_maxLines) {
                setMaxLines(typedArray.getInt(index, -1));
            } else if (index == R.styleable.EditText_android_minEms) {
                setMinEms(typedArray.getInt(index, getMinEms()));
            } else if (index == R.styleable.EditText_android_minLines) {
                setMinLines(typedArray.getInt(index, getMinLines()));
            } else if (index == R.styleable.EditText_android_privateImeOptions) {
                setPrivateImeOptions(typedArray.getString(index));
            } else if (index == R.styleable.EditText_android_scrollHorizontally) {
                if (typedArray.getBoolean(index, false)) {
                    setHorizontallyScrolling(true);
                }
            } else if (index == R.styleable.EditText_android_selectAllOnFocus) {
                setSelectAllOnFocus(typedArray.getBoolean(index, false));
            } else if (index == R.styleable.EditText_android_shadowColor) {
                shadowColor = typedArray.getInt(index, 0);
            } else if (index == R.styleable.EditText_android_shadowDx) {
                shadowDx = typedArray.getFloat(index, 0);
            } else if (index == R.styleable.EditText_android_shadowDy) {
                shadowDy = typedArray.getFloat(index, 0);
            } else if (index == R.styleable.EditText_android_shadowRadius) {
                shadowRadius = typedArray.getFloat(index, shadowRadius);
            } else if (index == R.styleable.EditText_android_singleLine) {
                setSingleLine(typedArray.getBoolean(index, false));
            } else if (index == R.styleable.EditText_android_text) {
                setText(typedArray.getText(index));
            } else if (index == R.styleable.EditText_android_textAllCaps) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    setAllCaps(typedArray.getBoolean(index, false));
                }
            } else if (index == R.styleable.EditText_android_textAppearance) {
                int resourceId = typedArray.getResourceId(index, -1);

                if (resourceId != -1 && resourceId != android.R.style.TextAppearance_Material) {
                    setTextAppearance(getContext(), resourceId);
                }
            } else if (index == R.styleable.EditText_android_textColor) {
                ColorStateList textColor = typedArray.getColorStateList(index);

                if (textColor != null) {
                    setTextColor(textColor);
                }
            } else if (index == R.styleable.EditText_android_textColorHighlight) {
                int highlightColor = typedArray.getColor(index, -1);

                if (highlightColor != -1) {
                    setHighlightColor(highlightColor);
                }
            } else if (index == R.styleable.EditText_android_textColorHint) {
                setHintTextColor(typedArray.getColorStateList(index));
            } else if (index == R.styleable.EditText_android_textColorLink) {
                setLinkTextColor(typedArray.getColorStateList(index));
            } else if (index == R.styleable.EditText_android_textIsSelectable) {
                setTextIsSelectable(typedArray.getBoolean(index, isTextSelectable()));
            } else if (index == R.styleable.EditText_android_textScaleX) {
                setTextScaleX(typedArray.getFloat(index, getTextScaleX()));
            } else if (index == R.styleable.EditText_android_textSize) {
                setTextSize(typedArray.getFloat(index, getTextSize()));
            } else if (index == R.styleable.EditText_android_typeface) {
                int typeface = typedArray.getInt(index, -1);

                if (typeface != -1) {
                    setTypeface(parseTypeface(typeface));
                }
            }
        }

        setCompoundDrawablesWithIntrinsicBounds(drawableLeft, drawableTop, drawableRight,
                drawableBottom);
        setImeActionLabel(imeActionLabel, imeActionId);

        if (lineSpacingExtra != -1 && lineSpacingMultiplier != -1) {
            setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
        }

        if (shadowColor != 0) {
            setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
        }
    }

    /**
     * Returns the value of the enum {@link TruncateAt}, which corresponds to a specific value.
     *
     * @param value
     *         The value
     * @return The value of the enum {@link TruncateAt}, which corresponds to the given value
     */
    private TruncateAt parseEllipsize(final int value) {
        switch (value) {
            case ELLIPSIZE_START_VALUE:
                return TruncateAt.START;
            case ELLIPSIZE_MIDDLE_VALUE:
                return TruncateAt.MIDDLE;
            case ELLIPSIZE_END_VALUE:
                return TruncateAt.END;
            case ELLIPSIZE_MARQUEE_VALUE:
                return TruncateAt.MARQUEE;
            default:
                return TruncateAt.END;
        }
    }

    /**
     * Returns the value of the enum {@link Typeface}, which corresponds to a specific value.
     *
     * @param value
     *         The value
     * @return The value of the enum {@link Typeface}, which corresponds to the given value
     */
    private Typeface parseTypeface(final int value) {
        switch (value) {
            case TYPEFACE_SANS_SERIF_VALUE:
                return Typeface.SANS_SERIF;
            case TYPEFACE_SERIF_VALUE:
                return Typeface.SERIF;
            case TYPEFACE_MONOSPACE_VALUE:
                return Typeface.MONOSPACE;
            default:
                return Typeface.DEFAULT;
        }
    }

    /**
     * Creates and returns a listener, which allows to validate the value of the view, when its text
     * has been changed.
     *
     * @return The listener, which has been created, as an instance of the type {@link TextWatcher}
     */
    private TextWatcher createTextChangeListener() {
        return new TextWatcher() {

            @Override
            public final void beforeTextChanged(final CharSequence s, final int start,
                                                final int count, final int after) {

            }

            @Override
            public final void onTextChanged(final CharSequence s, final int start, final int before,
                                            final int count) {

            }

            @Override
            public final void afterTextChanged(final Editable s) {
                if (isValidatedOnValueChange()) {
                    validate();
                }

                adaptMaxNumberOfCharactersMessage();
            }

        };
    }

    /**
     * Returns the message, which shows how many characters, in relation to the maximum number of
     * characters, the edit text is allowed to contain, have already been entered.
     *
     * @return The message, which shows how many characters, in relation to the maximum number of
     * characters, the edit text is allowed to contain, have already been typed, as an instance of
     * the type {@link CharSequence}
     */
    private CharSequence getMaxNumberOfCharactersMessage() {
        int maxLength = getMaxNumberOfCharacters();
        int currentLength = getView().length();
        return String
                .format(getResources().getString(R.string.edit_text_size_violation_error_message),
                        currentLength, maxLength);
    }

    /**
     * Adapts the text view, which shows the message, which shows how many characters, in relation
     * ot the maximum number of characters, the edit text is allowed to contain, have already been
     * entered.
     */
    private void adaptMaxNumberOfCharactersMessage() {
        if (getMaxNumberOfCharacters() != -1) {
            setRightMessage(getMaxNumberOfCharactersMessage(),
                    getView().length() > getMaxNumberOfCharacters());
        } else {
            setRightMessage(null);
        }
    }

    @Override
    protected final Collection<Validator<CharSequence>> onGetRightErrorMessage() {
        CharSequence errorMessage = getMaxNumberOfCharactersMessage();

        if (getMaxNumberOfCharacters() != -1) {
            Validator<CharSequence> validator =
                    Validators.maxLength(errorMessage, getMaxNumberOfCharacters());

            if (!validator.validate(getValue())) {
                Collection<Validator<CharSequence>> result = new LinkedList<>();
                result.add(validator);
                return result;
            }
        }

        return null;
    }

    @Override
    protected final void onValidate(final boolean valid) {
        adaptMaxNumberOfCharactersMessage();
    }

    @Override
    protected final android.widget.EditText createView() {
        return new android.widget.EditText(getContext());
    }

    @Override
    protected final ViewGroup createParentView() {
        return null;
    }

    @Override
    protected final CharSequence getValue() {
        return getView().getText();
    }

    /**
     * Creates a new view, which allows to enter text.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     */
    public EditText(@NonNull final Context context) {
        super(context);
        initialize(null);
    }

    /**
     * Creates a new view, which allows to enter text.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attributes of the XML tag that is inflating the view, as an instance of the type
     *         {@link AttributeSet} or null, if no attributes are available
     */
    public EditText(@NonNull final Context context, @Nullable final AttributeSet attributeSet) {
        super(context, attributeSet);
        initialize(attributeSet);
    }

    /**
     * Creates a new view, which allows to enter text.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attributes of the XML tag that is inflating the view, as an instance of the type
     *         {@link AttributeSet} or null, if no attributes are available
     * @param defaultStyle
     *         The default style to apply to this preference. If 0, no style will be applied (beyond
     *         what is included in the theme). This may either be an attribute resource, whose value
     *         will be retrieved from the current theme, or an explicit style resource
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public EditText(@NonNull final Context context, @Nullable final AttributeSet attributeSet,
                    final int defaultStyle) {
        super(context, attributeSet, defaultStyle);
        initialize(attributeSet);
    }

    /**
     * Creates a new view, which allows to enter text.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attributes of the XML tag that is inflating the view, as an instance of the type
     *         {@link AttributeSet} or null, if no attributes are available
     * @param defaultStyle
     *         The default style to apply to this preference. If 0, no style will be applied (beyond
     *         what is included in the theme). This may either be an attribute resource, whose value
     *         will be retrieved from the current theme, or an explicit style resource
     * @param defaultStyleResource
     *         A resource identifier of a style resource that supplies default values for the
     *         preference, used only if the default style is 0 or can not be found in the theme. Can
     *         be 0 to not look for defaults
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public EditText(@NonNull final Context context, @Nullable final AttributeSet attributeSet,
                    final int defaultStyle, final int defaultStyleResource) {
        super(context, attributeSet, defaultStyle, defaultStyleResource);
        initialize(attributeSet);
    }

    /**
     * Returns the maximum number of characters, the edit text is allowed to contain.
     *
     * @return The maximum number of characters, the edit text is allowed to contain, as an {@link
     * Integer} value or -1, if the number of characters is not restricted
     */
    public final int getMaxNumberOfCharacters() {
        return maxNumberOfCharacters;
    }

    /**
     * Sets the maximum number of characters, the edit text should be allowed to contain.
     *
     * @param maxNumberOfCharacters
     *         The maximum number of characters, which should be set, as an {@link Integer} value.
     *         The maximum number of characters must be at least 1 or -1, if the number of
     *         characters should not be restricted
     */
    public final void setMaxNumberOfCharacters(final int maxNumberOfCharacters) {
        if (maxNumberOfCharacters != -1) {
            Condition.INSTANCE.ensureAtLeast(maxNumberOfCharacters, 1,
                    "The maximum number of characters must be at least 1");
        }

        this.maxNumberOfCharacters = maxNumberOfCharacters;
        adaptMaxNumberOfCharactersMessage();
    }

    // ------------- Methods of the class android.widget.EditText -------------

    /**
     * Gets the autolink mask of the text. See {@link android.text.util.Linkify#ALL Linkify.ALL} and
     * peers for possible values.
     */
    public final int getAutoLinkMask() {
        return getView().getAutoLinkMask();
    }

    /**
     * Sets the autolink mask of the text. See {@link android.text.util.Linkify#ALL Linkify.ALL} and
     * peers for possible values.
     */
    public final void setAutoLinkMask(final int mask) {
        getView().setAutoLinkMask(mask);
    }

    /**
     * @return whether or not the cursor is visible (assuming this TextView is editable)
     * @see #setCursorVisible(boolean)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final boolean isCursorVisible() {
        return getView().isCursorVisible();
    }

    /**
     * Set whether the cursor is visible. The default is true. Note that this property only makes
     * sense for editable TextView.
     *
     * @see #isCursorVisible()
     */
    public final void setCursorVisible(final boolean visible) {
        getView().setCursorVisible(visible);
    }

    /**
     * Returns the padding between the compound drawables and the text.
     */
    public final int getCompoundDrawablePadding() {
        return getView().getCompoundDrawablePadding();
    }

    /**
     * Sets the size of the padding between the compound drawables and the text.
     */
    public final void setCompoundDrawablePadding(final int pad) {
        getView().setCompoundDrawablePadding(pad);
    }

    /**
     * Returns the top padding of the view, plus space for the top Drawable if any.
     */
    public final int getCompoundPaddingTop() {
        return getView().getCompoundPaddingTop();
    }

    /**
     * Returns the bottom padding of the view, plus space for the bottom Drawable if any.
     */
    public final int getCompoundPaddingBottom() {
        return getView().getCompoundPaddingBottom();
    }

    /**
     * Returns the left padding of the view, plus space for the left Drawable if any.
     */
    public final int getCompoundPaddingLeft() {
        return getView().getCompoundPaddingLeft();
    }

    /**
     * Returns the right padding of the view, plus space for the right Drawable if any.
     */
    public final int getCompoundPaddingRight() {
        return getView().getCompoundPaddingRight();
    }

    /**
     * Returns the start padding of the view, plus space for the start Drawable if any.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final int getCompoundPaddingStart() {
        return getView().getCompoundPaddingStart();
    }

    /**
     * Returns the end padding of the view, plus space for the end Drawable if any.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final int getCompoundPaddingEnd() {
        return getView().getCompoundPaddingEnd();
    }

    /**
     * Returns the extended top padding of the view, including both the top Drawable if any and any
     * extra space to keep more than maxLines of text from showing. It is only valid to call this
     * after measuring.
     */
    public final int getExtendedPaddingTop() {
        return getView().getExtendedPaddingTop();
    }

    /**
     * Returns the extended bottom padding of the view, including both the bottom Drawable if any
     * and any extra space to keep more than maxLines of text from showing. It is only valid to call
     * this after measuring.
     */
    public final int getExtendedPaddingBottom() {
        return getView().getExtendedPaddingBottom();
    }

    /**
     * Returns the total left padding of the view, including the left Drawable if any.
     */
    public final int getTotalPaddingLeft() {
        return getView().getTotalPaddingLeft();
    }

    /**
     * Returns the total right padding of the view, including the right Drawable if any.
     */
    public final int getTotalPaddingRight() {
        return getView().getTotalPaddingRight();
    }

    /**
     * Returns the total start padding of the view, including the start Drawable if any.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final int getTotalPaddingStart() {
        return getView().getTotalPaddingStart();
    }

    /**
     * Returns the total end padding of the view, including the end Drawable if any.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final int getTotalPaddingEnd() {
        return getView().getTotalPaddingEnd();
    }

    /**
     * Returns the total top padding of the view, including the top Drawable if any, the extra space
     * to keep more than maxLines from showing, and the vertical offset for gravity, if any.
     */
    public final int getTotalPaddingTop() {
        return getView().getTotalPaddingTop();
    }

    /**
     * Returns the total bottom padding of the view, including the bottom Drawable if any, the extra
     * space to keep more than maxLines from showing, and the vertical offset for gravity, if any.
     */
    public final int getTotalPaddingBottom() {
        return getView().getTotalPaddingBottom();
    }

    /**
     * Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the
     * text. Use {@code null} if you do not want a Drawable there. The Drawables must already have
     * had {@link Drawable#setBounds} called. <p> Calling this method will overwrite any Drawables
     * previously set using {@link #setCompoundDrawablesRelative} or related methods.
     */
    public final void setCompoundDrawables(final Drawable left, final Drawable top,
                                           final Drawable right, final Drawable bottom) {
        getView().setCompoundDrawables(left, top, right, bottom);
    }

    /**
     * Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the
     * text. Use 0 if you do not want a Drawable there. The Drawables' bounds will be set to their
     * intrinsic bounds. <p> Calling this method will overwrite any Drawables previously set using
     * {@link #setCompoundDrawablesRelative} or related methods.
     *
     * @param left
     *         Resource identifier of the left Drawable.
     * @param top
     *         Resource identifier of the top Drawable.
     * @param right
     *         Resource identifier of the right Drawable.
     * @param bottom
     *         Resource identifier of the bottom Drawable.
     */
    public final void setCompoundDrawablesWithIntrinsicBounds(final int left, final int top,
                                                              final int right, final int bottom) {
        getView().setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
    }

    /**
     * Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the
     * text. Use {@code null} if you do not want a Drawable there. The Drawables' bounds will be set
     * to their intrinsic bounds. <p> Calling this method will overwrite any Drawables previously
     * set using {@link #setCompoundDrawablesRelative} or related methods.
     */
    public final void setCompoundDrawablesWithIntrinsicBounds(final Drawable left,
                                                              final Drawable top,
                                                              final Drawable right,
                                                              final Drawable bottom) {
        getView().setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
    }

    /**
     * Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the
     * text. Use {@code null} if you do not want a Drawable there. The Drawables must already have
     * had {@link Drawable#setBounds} called. <p> Calling this method will overwrite any Drawables
     * previously set using {@link #setCompoundDrawables} or related methods.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final void setCompoundDrawablesRelative(final Drawable start, final Drawable top,
                                                   final Drawable end, final Drawable bottom) {
        getView().setCompoundDrawablesRelative(start, top, end, bottom);
    }

    /**
     * Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the
     * text. Use 0 if you do not want a Drawable there. The Drawables' bounds will be set to their
     * intrinsic bounds. <p> Calling this method will overwrite any Drawables previously set using
     * {@link #setCompoundDrawables} or related methods.
     *
     * @param start
     *         Resource identifier of the start Drawable.
     * @param top
     *         Resource identifier of the top Drawable.
     * @param end
     *         Resource identifier of the end Drawable.
     * @param bottom
     *         Resource identifier of the bottom Drawable.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final void setCompoundDrawablesRelativeWithIntrinsicBounds(final int start,
                                                                      final int top, final int end,
                                                                      final int bottom) {
        getView().setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
    }

    /**
     * Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the
     * text. Use {@code null} if you do not want a Drawable there. The Drawables' bounds will be set
     * to their intrinsic bounds. <p> Calling this method will overwrite any Drawables previously
     * set using {@link #setCompoundDrawables} or related methods.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final void setCompoundDrawablesRelativeWithIntrinsicBounds(final Drawable start,
                                                                      final Drawable top,
                                                                      final Drawable end,
                                                                      final Drawable bottom) {
        getView().setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
    }

    /**
     * Returns drawables for the left, top, right, and bottom borders.
     */
    public final Drawable[] getCompoundDrawables() {
        return getView().getCompoundDrawables();
    }

    /**
     * Returns drawables for the start, top, end, and bottom borders.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final Drawable[] getCompoundDrawablesRelative() {
        return getView().getCompoundDrawablesRelative();
    }

    /**
     * Set the TextView's elegant height metrics flag. This setting selects font variants that have
     * not been compacted to fit Latin-based vertical metrics, and also increases top and bottom
     * bounds to provide more space.
     *
     * @param elegant
     *         set the paint's elegant metrics flag.
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final void setElegantTextHeight(final boolean elegant) {
        getView().setElegantTextHeight(elegant);
    }

    /**
     * Returns where, if anywhere, words that are longer than the view is wide should be
     * ellipsized.
     */
    public final TextUtils.TruncateAt getEllipsize() {
        return getView().getEllipsize();
    }

    /**
     * Causes words in the text that are longer than the view is wide to be ellipsized instead of
     * broken in the middle. You may also want to {@link #setSingleLine} or {@link
     * #setHorizontallyScrolling} to constrain the text to a single line. Use <code>null</code> to
     * turn off ellipsizing.
     * <p>
     * If {@link #setMaxLines} has been used to set two or more lines, only {@link
     * android.text.TextUtils.TruncateAt#END} and {@link android.text.TextUtils.TruncateAt#MARQUEE}
     * are supported (other ellipsizing types will not do anything).
     */
    public final void setEllipsize(final TextUtils.TruncateAt where) {
        getView().setEllipsize(where);
    }

    /**
     * Makes the TextView exactly this many ems wide
     *
     * @see #setMaxEms(int)
     * @see #setMinEms(int)
     * @see #getMinEms()
     * @see #getMaxEms()
     */
    public final void setEms(final int ems) {
        getView().setEms(ems);
    }

    /**
     * @return the currently set font feature settings. Default is null.
     * @see #setFontFeatureSettings(String)
     * @see Paint#setFontFeatureSettings
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final String getFontFeatureSettings() {
        return getView().getFontFeatureSettings();
    }

    /**
     * Sets font feature settings. The format is the same as the CSS font-feature-settings
     * attribute: http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
     *
     * @param fontFeatureSettings
     *         font feature settings represented as CSS compatible string
     * @see #getFontFeatureSettings()
     * @see Paint#getFontFeatureSettings
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final void setFontFeatureSettings(final String fontFeatureSettings) {
        getView().setFontFeatureSettings(fontFeatureSettings);
    }

    /**
     * Return whether this text view is including its entire text contents in frozen icicles.
     *
     * @return Returns true if text is included, false if it isn't.
     * @see #setFreezesText
     */
    public final boolean getFreezesText() {
        return getView().getFreezesText();
    }

    /**
     * Control whether this text view saves its entire text contents when freezing to an icicle, in
     * addition to dynamic state such as cursor position. By default this is false, not saving the
     * text. Set to true if the text in the text view is not being saved somewhere else in
     * persistent storage (such as in a content provider) so that if the view is later thawed the
     * user will not lose their data.
     *
     * @param freezesText
     *         Controls whether a frozen icicle should include the entire text data: true to include
     *         it, false to not.
     */
    public final void setFreezesText(final boolean freezesText) {
        getView().setFreezesText(freezesText);
    }

    /**
     * Returns the hint that is displayed when the text of the TextView is empty.
     */
    public final CharSequence getHint() {
        return getView().getHint();
    }

    /**
     * Sets the text to be displayed when the text of the TextView is empty. Null means to use the
     * normal empty text. The hint does not currently participate in determining the size of the
     * view.
     */
    public final void setHint(final CharSequence hint) {
        getView().setHint(hint);
    }

    /**
     * Sets the text to be displayed when the text of the TextView is empty, from a resource.
     */
    public final void setHint(final int resid) {
        getView().setHint(resid);
    }

    /**
     * Change the editor type integer associated with the text view, which will be reported to an
     * IME with {@link EditorInfo#imeOptions} when it has focus.
     *
     * @see #getImeOptions
     * @see android.view.inputmethod.EditorInfo
     */
    public final void setImeOptions(final int imeOptions) {
        getView().setImeOptions(imeOptions);
    }

    /**
     * Get the type of the IME editor.
     *
     * @see #setImeOptions(int)
     * @see android.view.inputmethod.EditorInfo
     */
    public final int getImeOptions() {
        return getView().getImeOptions();
    }

    /**
     * Change the custom IME action associated with the text view, which will be reported to an IME
     * with {@link EditorInfo#actionLabel} and {@link EditorInfo#actionId} when it has focus.
     *
     * @see #getImeActionLabel
     * @see #getImeActionId
     * @see android.view.inputmethod.EditorInfo
     */
    public final void setImeActionLabel(final CharSequence label, final int actionId) {
        getView().setImeActionLabel(label, actionId);
    }

    /**
     * Get the IME action label previous set with {@link #setImeActionLabel}.
     *
     * @see #setImeActionLabel
     * @see android.view.inputmethod.EditorInfo
     */
    public final CharSequence getImeActionLabel() {
        return getView().getImeActionLabel();
    }

    /**
     * Get the IME action ID previous set with {@link #setImeActionLabel}.
     *
     * @see #setImeActionLabel
     * @see android.view.inputmethod.EditorInfo
     */
    public final int getImeActionId() {
        return getView().getImeActionId();
    }

    /**
     * Set whether the TextView includes extra top and bottom padding to make room for accents that
     * go above the normal ascent and descent. The default is true.
     *
     * @see #getIncludeFontPadding()
     */
    public final void setIncludeFontPadding(final boolean includepad) {
        getView().setIncludeFontPadding(includepad);
    }

    /**
     * Gets whether the TextView includes extra top and bottom padding to make room for accents that
     * go above the normal ascent and descent.
     *
     * @see #setIncludeFontPadding(boolean)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final boolean getIncludeFontPadding() {
        return getView().getIncludeFontPadding();
    }

    /**
     * Get the type of the editable content.
     *
     * @see #setInputType(int)
     * @see android.text.InputType
     */
    public final int getInputType() {
        return getView().getInputType();
    }

    /**
     * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
     * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
     * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
     * then a soft keyboard will not be displayed for this text view.
     * <p>
     * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
     * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
     * type.
     *
     * @see #getInputType()
     * @see #setRawInputType(int)
     * @see android.text.InputType
     */
    public final void setInputType(final int type) {
        getView().setInputType(type);
    }

    /**
     * @return the extent by which text is currently being letter-spaced. This will normally be 0.
     * @see #setLetterSpacing(float)
     * @see Paint#setLetterSpacing
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final float getLetterSpacing() {
        return getView().getLetterSpacing();
    }

    /**
     * Sets text letter-spacing. The value is in 'EM' units. Typical values for slight expansion
     * will be around 0.05. Negative values tighten text.
     *
     * @see #getLetterSpacing()
     * @see Paint#getLetterSpacing
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final void setLetterSpacing(final float letterSpacing) {
        getView().setLetterSpacing(letterSpacing);
    }

    /**
     * Makes the TextView exactly this many lines tall.
     * <p>
     * Note that setting this value overrides any other (minimum / maximum) number of lines or
     * height setting. A single line TextView will set this value to 1.
     */
    public final void setLines(final int lines) {
        getView().setLines(lines);
    }

    /**
     * Sets line spacing for this TextView. Each line will have its height multiplied by
     * <code>mult</code> and have <code>add</code> added to it.
     */
    public final void setLineSpacing(final float add, final float mult) {
        getView().setLineSpacing(add, mult);
    }

    /**
     * Gets the line spacing multiplier
     *
     * @return the value by which each line's height is multiplied to get its actual height.
     * @see #setLineSpacing(float, float)
     * @see #getLineSpacingExtra()
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final float getLineSpacingMultiplier() {
        return getView().getLineSpacingMultiplier();
    }

    /**
     * Gets the line spacing extra space
     *
     * @return the extra space that is added to the height of each lines of this TextView.
     * @see #setLineSpacing(float, float)
     * @see #getLineSpacingMultiplier()
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final float getLineSpacingExtra() {
        return getView().getLineSpacingExtra();
    }

    /**
     * Sets whether the movement method will automatically be set to {@link LinkMovementMethod} if
     * {@link #setAutoLinkMask} has been set to nonzero and links are detected in {@link #setText}.
     * The default is true.
     */
    public final void setLinksClickable(final boolean whether) {
        getView().setLinksClickable(whether);
    }

    /**
     * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
     * if {@link #setAutoLinkMask} has been set to nonzero and links are detected in {@link
     * #setText}. The default is true.
     */
    public final boolean getLinksClickable() {
        return getView().getLinksClickable();
    }

    /**
     * Sets how many times to repeat the marquee animation. Only applied if the TextView has marquee
     * enabled. Set to -1 to repeat indefinitely.
     *
     * @see #getMarqueeRepeatLimit()
     */
    public final void setMarqueeRepeatLimit(final int marqueeLimit) {
        getView().setMarqueeRepeatLimit(marqueeLimit);
    }

    /**
     * Gets the number of times the marquee animation is repeated. Only meaningful if the TextView
     * has marquee enabled.
     *
     * @return the number of times the marquee animation is repeated. -1 if the animation repeats
     * indefinitely
     * @see #setMarqueeRepeatLimit(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getMarqueeRepeatLimit() {
        return getView().getMarqueeRepeatLimit();
    }

    /**
     * Makes the TextView at most this many ems wide
     */
    public final void setMaxEms(final int maxems) {
        getView().setMaxEms(maxems);
    }

    /**
     * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width was
     * set in pixels instead (using setMaxWidth(int) or setWidth(int)).
     * @see #setMaxEms(int)
     * @see #setEms(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getMaxEms() {
        return getView().getMaxEms();
    }

    /**
     * Sets the list of input filters that will be used if the buffer is Editable. Has no effect
     * otherwise.
     */
    public final void setFilters(final InputFilter[] filters) {
        getView().setFilters(filters);
    }

    /**
     * Returns the current list of input filters.
     */
    public final InputFilter[] getFilters() {
        return getView().getFilters();
    }

    /**
     * Makes the TextView at most this many lines tall.
     * <p>
     * Setting this value overrides any other (maximum) height setting.
     */
    public final void setMaxLines(final int maxlines) {
        getView().setMaxLines(maxlines);
    }

    /**
     * @return the maximum number of lines displayed in this TextView, or -1 if the maximum height
     * was set in pixels instead using setMaxHeight(int) or setHeight(int).
     * @see #setMaxLines(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getMaxLines() {
        return getView().getMaxLines();
    }

    /**
     * Makes the TextView at least this many ems wide
     */
    public final void setMinEms(final int minems) {
        getView().setMinEms(minems);
    }

    /**
     * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width was
     * set in pixels instead (using setMinWidth(int) or setWidth(int)).
     * @see #setMinEms(int)
     * @see #setEms(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getMinEms() {
        return getView().getMinEms();
    }

    /**
     * Makes the TextView at least this many lines tall.
     * <p>
     * Setting this value overrides any other (minimum) height setting. A single line TextView will
     * set this value to 1.
     *
     * @see #getMinLines()
     */
    public final void setMinLines(final int minlines) {
        getView().setMinLines(minlines);
    }

    /**
     * @return the minimum number of lines displayed in this TextView, or -1 if the minimum height
     * was set in pixels instead using setMinHeight(int) or setHeight(int).
     * @see #setMinLines(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getMinLines() {
        return getView().getMinLines();
    }

    /**
     * Set the private content type of the text, which is the {@link EditorInfo#privateImeOptions
     * EditorInfo.privateImeOptions} field that will be filled in when creating an input
     * connection.
     *
     * @see #getPrivateImeOptions()
     * @see EditorInfo#privateImeOptions
     */
    public final void setPrivateImeOptions(final String type) {
        getView().setPrivateImeOptions(type);
    }

    /**
     * Get the private type of the content.
     *
     * @see #setPrivateImeOptions(String)
     * @see EditorInfo#privateImeOptions
     */
    public final String getPrivateImeOptions() {
        return getView().getPrivateImeOptions();
    }

    /**
     * Set the TextView so that when it takes focus, all the text is selected.
     */
    public final void setSelectAllOnFocus(final boolean selectAllOnFocus) {
        getView().setSelectAllOnFocus(selectAllOnFocus);
    }

    /**
     * Gives the text a shadow of the specified blur radius and color, the specified distance from
     * its drawn position. <p> The text shadow produced does not interact with the properties on
     * view that are responsible for real time shadows, {@link View#getElevation() elevation} and
     * {@link View#getTranslationZ() translationZ}.
     *
     * @see Paint#setShadowLayer(float, float, float, int)
     */
    public final void setShadowLayer(final float radius, final float dx, final float dy,
                                     final int color) {
        getView().setShadowLayer(radius, dx, dy, color);
    }

    /**
     * Gets the radius of the shadow layer.
     *
     * @return the radius of the shadow layer. If 0, the shadow layer is not visible
     * @see #setShadowLayer(float, float, float, int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final float getShadowRadius() {
        return getView().getShadowRadius();
    }

    /**
     * @return the horizontal offset of the shadow layer
     * @see #setShadowLayer(float, float, float, int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final float getShadowDx() {
        return getView().getShadowDx();
    }

    /**
     * @return the vertical offset of the shadow layer
     * @see #setShadowLayer(float, float, float, int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final float getShadowDy() {
        return getView().getShadowDy();
    }

    /**
     * @return the color of the shadow layer
     * @see #setShadowLayer(float, float, float, int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getShadowColor() {
        return getView().getShadowColor();
    }

    /**
     * Sets the properties of this field (lines, horizontally scrolling, transformation method) to
     * be for a single-line input.
     */
    public final void setSingleLine() {
        getView().setSingleLine();
    }

    /**
     * If true, sets the properties of this field (number of lines, horizontally scrolling,
     * transformation method) to be for a single-line input; if false, restores these to the default
     * conditions.
     * <p>
     * Note that the default conditions are not necessarily those that were in effect prior this
     * method, and you may want to reset these properties to your custom values.
     */
    public final void setSingleLine(final boolean singleLine) {
        getView().setSingleLine(singleLine);
    }

    /**
     * Sets the string value of the TextView. TextView <em>does not</em> accept HTML-like
     * formatting, which you can do with text strings in XML resource files. To style your strings,
     * attach android.text.style.* objects to a {@link android.text.SpannableString
     * SpannableString}, or see the documentation for an example of setting formatted text in the
     * XML resource file.
     */
    public final void setText(final CharSequence text) {
        getView().setText(text);
    }

    /**
     * Like {@link #setText(CharSequence)}, except that the cursor position (if any) is retained in
     * the new text.
     *
     * @param text
     *         The new text to place in the text view.
     * @see #setText(CharSequence)
     */
    public final void setTextKeepState(final CharSequence text) {
        getView().setTextKeepState(text);
    }

    /**
     * Sets the text that this TextView is to display (see {@link #setText(CharSequence)}) and also
     * sets whether it is stored in a styleable/spannable buffer and whether it is editable.
     */
    public final void setText(final CharSequence text, final BufferType type) {
        getView().setText(text, type);
    }

    /**
     * Sets the TextView to display the specified slice of the specified char array. You must
     * promise that you will not change the contents of the array except for right before another
     * call to setText(), since the TextView has no way to know that the text has changed and that
     * it needs to invalidate and re-layout.
     */
    public final void setText(final char[] text, final int start, final int len) {
        getView().setText(text, start, len);
    }

    /**
     * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, except that the
     * cursor position (if any) is retained in the new text.
     *
     * @see #setText(CharSequence, android.widget.TextView.BufferType)
     */
    public final void setTextKeepState(final CharSequence text, final BufferType type) {
        getView().setTextKeepState(text, type);
    }

    public final void setText(final int resid) {
        getView().setText(resid);
    }

    public final void setText(final int resid, final BufferType type) {
        getView().setText(resid, type);
    }

    /**
     * Sets the properties of this field to transform input to ALL CAPS display. This may use a
     * "small caps" formatting if available. This setting will be ignored if this field is editable
     * or selectable.
     * <p>
     * This call replaces the current transformation method. Disabling this will not necessarily
     * restore the previous behavior from before this was enabled.
     *
     * @see #setTransformationMethod(TransformationMethod)
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public final void setAllCaps(final boolean allCaps) {
        getView().setAllCaps(allCaps);
    }

    /**
     * Sets the text color, size, style, hint color, and highlight color from the specified
     * TextAppearance resource.
     */
    public final void setTextAppearance(final Context context, final int resid) {
        getView().setTextAppearance(context, resid);
    }

    /**
     * Sets the text color for all the states (normal, selected, focused) to be this color.
     *
     * @see #setTextColor(ColorStateList)
     * @see #getTextColors()
     */
    public final void setTextColor(final int color) {
        getView().setTextColor(color);
    }

    /**
     * Sets the text color.
     *
     * @see #setTextColor(int)
     * @see #getTextColors()
     * @see #setHintTextColor(ColorStateList)
     * @see #setLinkTextColor(ColorStateList)
     */
    public final void setTextColor(final ColorStateList colors) {
        getView().setTextColor(colors);
    }

    /**
     * Gets the text colors for the different states (normal, selected, focused) of the TextView.
     *
     * @see #setTextColor(ColorStateList)
     * @see #setTextColor(int)
     */
    public final ColorStateList getTextColors() {
        return getView().getTextColors();
    }

    /**
     * <p> Return the current color selected for normal text. </p>
     *
     * @return Returns the current text color.
     */
    public final int getCurrentTextColor() {
        return getView().getCurrentTextColor();
    }

    /**
     * Sets the color used to display the selection highlight.
     */
    public final void setHighlightColor(final int color) {
        getView().setHighlightColor(color);
    }

    /**
     * @return the color used to display the selection highlight
     * @see #setHighlightColor(int)
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public final int getHighlightColor() {
        return getView().getHighlightColor();
    }

    /**
     * Sets whether the soft input method will be made visible when this TextView gets focused. The
     * default is true.
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final void setShowSoftInputOnFocus(final boolean show) {
        getView().setShowSoftInputOnFocus(show);
    }

    /**
     * Returns whether the soft input method will be made visible when this TextView gets focused.
     * The default is true.
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public final boolean getShowSoftInputOnFocus() {
        return getView().getShowSoftInputOnFocus();
    }

    /**
     * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
     * TextView.
     *
     * @see #setHintTextColor(ColorStateList)
     * @see #getHintTextColors()
     * @see #setTextColor(int)
     */
    public final void setHintTextColor(final int color) {
        getView().setHintTextColor(color);
    }

    /**
     * Sets the color of the hint text.
     *
     * @see #getHintTextColors()
     * @see #setHintTextColor(int)
     * @see #setTextColor(ColorStateList)
     * @see #setLinkTextColor(ColorStateList)
     */
    public final void setHintTextColor(final ColorStateList colors) {
        getView().setHintTextColor(colors);
    }

    /**
     * @return the color of the hint text, for the different states of this TextView.
     * @see #setHintTextColor(ColorStateList)
     * @see #setHintTextColor(int)
     * @see #setTextColor(ColorStateList)
     * @see #setLinkTextColor(ColorStateList)
     */
    public final ColorStateList getHintTextColors() {
        return getView().getHintTextColors();
    }

    /**
     * <p> Return the current color selected to paint the hint text. </p>
     *
     * @return Returns the current hint text color.
     */
    public final int getCurrentHintTextColor() {
        return getView().getCurrentHintTextColor();
    }

    /**
     * Sets the color of links in the text.
     *
     * @see #setLinkTextColor(ColorStateList)
     * @see #getLinkTextColors()
     */
    public final void setLinkTextColor(final int color) {
        getView().setLinkTextColor(color);
    }

    /**
     * Sets the color of links in the text.
     *
     * @see #setLinkTextColor(int)
     * @see #getLinkTextColors()
     * @see #setTextColor(ColorStateList)
     * @see #setHintTextColor(ColorStateList)
     */
    public final void setLinkTextColor(final ColorStateList colors) {
        getView().setLinkTextColor(colors);
    }

    /**
     * @return the list of colors used to paint the links in the text, for the different states of
     * this TextView
     * @see #setLinkTextColor(ColorStateList)
     * @see #setLinkTextColor(int)
     */
    public final ColorStateList getLinkTextColors() {
        return getView().getLinkTextColors();
    }

    /**
     * Sets whether the content of this view is selectable by the user. The default is {@code
     * false}, meaning that the content is not selectable. <p> When you use a TextView to display a
     * useful piece of information to the user (such as a contact's address), make it selectable, so
     * that the user can select and copy its content. You can also use set the XML attribute
     * android.R.styleable#TextView_textIsSelectable to "true". <p> When you call this method to set
     * the value of {@code textIsSelectable}, it sets the flags {@code focusable}, {@code
     * focusableInTouchMode}, {@code clickable}, and {@code longClickable} to the same value. These
     * flags correspond to the attributes android.R.styleable#View_focusable android:focusable,
     * android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode,
     * ndroid.R.styleable#View_clickable android:clickable, and android.R.styleable#View_longClickable
     * android:longClickable. To restore any of these flags to a state you had set previously, call
     * one or more of the following methods: {@link #setFocusable(boolean) setFocusable()}, {@link
     * #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, {@link #setClickable(boolean)
     * setClickable()} or {@link #setLongClickable(boolean) setLongClickable()}.
     *
     * @param selectable
     *         Whether the content of this TextView should be selectable.
     */
    public final void setTextIsSelectable(final boolean selectable) {
        getView().setTextIsSelectable(selectable);
    }

    /**
     * Returns the state of the {@code textIsSelectable} flag (See {@link #setTextIsSelectable
     * setTextIsSelectable()}). Although you have to set this flag to allow users to select and copy
     * text in a non-editable TextView, the content of an {@link EditText} can always be selected,
     * independently of the value of this flag. <p>
     *
     * @return True if the text displayed in this TextView can be selected by the user.
     */
    public final boolean isTextSelectable() {
        return getView().isTextSelectable();
    }

    /**
     * @return the extent by which text is currently being stretched horizontally. This will usually
     * be 1.
     */
    public final float getTextScaleX() {
        return getView().getTextScaleX();
    }

    /**
     * Sets the extent by which text should be stretched horizontally.
     */
    public final void setTextScaleX(final float size) {
        getView().setTextScaleX(size);
    }

    /**
     * Set the default text size to the given value, interpreted as "scaled pixel" units. This size
     * is adjusted based on the current density and user font size preference.
     *
     * @param size
     *         The scaled pixel size.
     */
    public final void setTextSize(final float size) {
        getView().setTextSize(size);
    }

    /**
     * Set the default text size to a given unit and value. See {@link TypedValue} for the possible
     * dimension units.
     *
     * @param unit
     *         The desired dimension unit.
     * @param size
     *         The desired size in the given units.
     */
    public final void setTextSize(final int unit, final float size) {
        getView().setTextSize(size);
    }

    /**
     * @return the size (in pixels) of the default text size in this TextView.
     */
    public final float getTextSize() {
        return getView().getTextSize();
    }

    /**
     * Sets the typeface and style in which the text should be displayed, and turns on the fake bold
     * and italic bits in the Paint if the Typeface that you provided does not have all the bits in
     * the style that you specified.
     */
    public final void setTypeface(final Typeface tf, final int style) {
        getView().setTypeface(tf, style);
    }

    /**
     * Sets the typeface and style in which the text should be displayed. Note that not all Typeface
     * families actually have bold and italic variants, so you may need to use {@link
     * #setTypeface(Typeface, int)} to get the appearance that you actually want.
     *
     * @see #getTypeface()
     */
    public final void setTypeface(final Typeface tf) {
        getView().setTypeface(tf);
    }

    /**
     * @return the current typeface and style in which the text is being displayed.
     * @see #setTypeface(Typeface)
     */
    public final Typeface getTypeface() {
        return getView().getTypeface();
    }

    /**
     * Returns the length, in characters, of the text managed by this TextView
     */
    public final int length() {
        return getView().length();
    }

    /**
     * Return the text the TextView is displaying as an Editable object. If the text is not
     * editable, null is returned.
     *
     * @see #getText
     */
    public final Editable getEditableText() {
        return getView().getEditableText();
    }

    /**
     * @return the height of one standard line in pixels. Note that markup within the text can cause
     * individual lines to be taller or shorter than this height, and the layout may contain
     * additional first- or last-line padding.
     */
    public final int getLineHeight() {
        return getView().getLineHeight();
    }

    /**
     * @return the Layout that is currently being used to display the text. This can be null if the
     * text or width has recently changes.
     */
    public final Layout getLayout() {
        return getView().getLayout();
    }

    /**
     * @return the current key listener for this TextView. This will frequently be null for
     * non-EditText TextViews.
     */
    public final KeyListener getKeyListener() {
        return getView().getKeyListener();
    }

    /**
     * Sets the key listener to be used with this TextView. This can be null to disallow user input.
     * Note that this method has significant and subtle interactions with soft keyboards and other
     * input method: see {@link KeyListener#getInputType() KeyListener.getContentType()} for
     * important details. Calling this method will replace the current content type of the text view
     * with the content type returned by the key listener. <p> Be warned that if you want a TextView
     * with a key listener or movement method not to be focusable, or if you want a TextView without
     * a key listener or movement method to be focusable, you must call {@link #setFocusable} again
     * after calling this to get the focusability back the way you want it.
     */
    public final void setKeyListener(final KeyListener input) {
        getView().setKeyListener(input);
    }

    /**
     * @return the movement method being used for this TextView. This will frequently be null for
     * non-EditText TextViews.
     */
    public final MovementMethod getMovementMethod() {
        return getView().getMovementMethod();
    }

    /**
     * Sets the movement method (arrow key handler) to be used for this TextView. This can be null
     * to disallow using the arrow keys to move the cursor or scroll the view. <p> Be warned that if
     * you want a TextView with a key listener or movement method not to be focusable, or if you
     * want a TextView without a key listener or movement method to be focusable, you must call
     * {@link #setFocusable} again after calling this to get the focusability back the way you want
     * it.
     */
    public final void setMovementMethod(final MovementMethod movement) {
        getView().setMovementMethod(movement);
    }

    /**
     * @return the current transformation method for this TextView. This will frequently be null
     * except for single-line and password fields.
     */
    public final TransformationMethod getTransformationMethod() {
        return getView().getTransformationMethod();
    }

    /**
     * Sets the transformation that is applied to the text that this TextView is displaying.
     */
    public final void setTransformationMethod(final TransformationMethod method) {
        getView().setTransformationMethod(method);
    }

    /**
     * Get the default {@link Locale} of the text in this TextView.
     *
     * @return the default {@link Locale} of the text in this TextView.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final Locale getTextLocale() {
        return getView().getTextLocale();
    }

    /**
     * Set the default {@link Locale} of the text in this TextView to the given value. This value is
     * used to choose appropriate typefaces for ambiguous characters. Typically used for CJK locales
     * to disambiguate Hanzi/Kanji/Hanja characters.
     *
     * @param locale
     *         the {@link Locale} for drawing text, must not be null.
     * @see Paint#setTextLocale
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public final void setTextLocale(final Locale locale) {
        getView().setTextLocale(locale);
    }

    /**
     * @return the base paint used for the text. Please use this only to consult the Paint's
     * properties and not to change them.
     */
    public final TextPaint getPaint() {
        return getView().getPaint();
    }

    /**
     * Returns the list of URLSpans attached to the text (by {@link Linkify} or otherwise) if any.
     * You can call {@link URLSpan#getURL} on them to find where they link to or use {@link
     * Spanned#getSpanStart} and {@link Spanned#getSpanEnd} to find the region of the text they are
     * attached to.
     */
    public final URLSpan[] getUrls() {
        return getView().getUrls();
    }

    /**
     * Sets the horizontal alignment of the text and the vertical gravity that will be used when
     * there is extra space in the TextView beyond what is required for the text itself.
     *
     * @see android.view.Gravity
     */
    public final void setTextGravity(final int gravity) {
        getView().setGravity(gravity);
    }

    /**
     * Returns the horizontal and vertical alignment of this TextView.
     *
     * @see android.view.Gravity
     */
    public final int getTextGravity() {
        return getView().getGravity();
    }

    /**
     * @return the flags on the Paint being used to display the text.
     * @see Paint#getFlags
     */
    public final int getPaintFlags() {
        return getView().getPaintFlags();
    }

    /**
     * Sets flags on the Paint being used to display the text and reflows the text if they are
     * different from the old flags.
     *
     * @see Paint#setFlags
     */
    public final void setPaintFlags(final int flags) {
        getView().setPaintFlags(flags);
    }

    /**
     * Sets whether the text should be allowed to be wider than the View is. If false, it will be
     * wrapped to the width of the View.
     */
    public final void setHorizontallyScrolling(final boolean whether) {
        getView().setHorizontallyScrolling(whether);
    }

    /**
     * Convenience method: Append the specified text to the TextView's display buffer, upgrading it
     * to BufferType.EDITABLE if it was not already editable.
     */
    public final void append(final CharSequence text) {
        getView().append(text);
    }

    /**
     * Convenience method: Append the specified text slice to the TextView's display buffer,
     * upgrading it to BufferType.EDITABLE if it was not already editable.
     */
    public final void append(final CharSequence text, final int start, final int end) {
        getView().append(text, start, end);
    }

    /**
     * Sets the Factory used to create new Editables.
     */
    public final void setEditableFactory(final Editable.Factory factory) {
        getView().setEditableFactory(factory);
    }

    /**
     * Sets the Factory used to create new Spannables.
     */
    public final void setSpannableFactory(final Spannable.Factory factory) {
        getView().setSpannableFactory(factory);
    }

    /**
     * Directly change the content type integer of the text view, without modifying any other
     * state.
     *
     * @see #setInputType(int)
     * @see android.text.InputType
     */
    public final void setRawInputType(final int type) {
        getView().setRawInputType(type);
    }

    /**
     * Set a special listener to be called when an action is performed on the text view. This will
     * be called when the enter key is pressed, or when an action supplied to the IME is selected by
     * the user. Setting this means that the normal hard key event will not insert a newline into
     * the text view, even if it is multi-line; holding down the ALT modifier will, however, allow
     * the user to insert a newline character.
     */
    public final void setOnEditorActionListener(final OnEditorActionListener l) {
        getView().setOnEditorActionListener(l);
    }

    /**
     * Set the extra input data of the text, which is the {@link EditorInfo#extras
     * TextBoxAttribute.extras} Bundle that will be filled in when creating an input connection. The
     * given integer is the resource ID of an XML resource holding an android.R.styleable#InputExtras
     * &lt;input-extras&gt; XML tree.
     *
     * @see #getInputExtras(boolean)
     * @see EditorInfo#extras
     */
    public final void setInputExtras(final int xmlResId)
            throws XmlPullParserException, IOException {
        getView().setInputExtras(xmlResId);
    }

    /**
     * Retrieve the input extras currently associated with the text view, which can be viewed as
     * well as modified.
     *
     * @param create
     *         If true, the extras will be created if they don't already exist. Otherwise, null will
     *         be returned if none have been created.
     * @see #setInputExtras(int)
     * @see EditorInfo#extras
     */
    public final Bundle getInputExtras(final boolean create) {
        return getView().getInputExtras(create);
    }

    /**
     * Return the number of lines of text, or 0 if the internal Layout has not been built.
     */
    public final int getLineCount() {
        return getView().getLineCount();
    }

    /**
     * Return the baseline for the specified line (0...getLineCount() - 1) If bounds is not null,
     * return the top, left, right, bottom extents of the specified line in it. If the internal
     * Layout has not been built, return 0 and set bounds to (0, 0, 0, 0)
     *
     * @param line
     *         which line to examine (0..getLineCount() - 1)
     * @param bounds
     *         Optional. If not null, it returns the extent of the line
     * @return the Y-coordinate of the baseline
     */
    public final int getLineBounds(final int line, final Rect bounds) {
        return getView().getLineBounds(line, bounds);
    }

    /**
     * If this TextView contains editable content, extract a portion of it based on the information
     * in <var>request</var> in to <var>outText</var>.
     *
     * @return Returns true if the text was successfully extracted, else false.
     */
    public final boolean extractText(final ExtractedTextRequest request,
                                     final ExtractedText outText) {
        return getView().extractText(request, outText);
    }

    /**
     * Apply to this text view the given extracted text, as previously returned by {@link
     * #extractText(ExtractedTextRequest, ExtractedText)}.
     */
    public final void setExtractedText(final ExtractedText text) {
        getView().setExtractedText(text);
    }

    public final void beginBatchEdit() {
        getView().beginBatchEdit();
    }

    public final void endBatchEdit() {
        getView().endBatchEdit();
    }

    /**
     * Move the point, specified by the offset, into the view if it is needed. This has to be called
     * after layout. Returns true if anything changed.
     */
    public final boolean bringPointIntoView(final int offset) {
        return getView().bringPointIntoView(offset);
    }

    /**
     * Move the cursor, if needed, so that it is at an offset that is visible to the user. This will
     * not move the cursor if it represents more than one character (a selection range). This will
     * only work if the TextView contains spannable text; otherwise it will do nothing.
     *
     * @return True if the cursor was actually moved, false otherwise.
     */
    public final boolean moveCursorToVisibleOffset() {
        return getView().moveCursorToVisibleOffset();
    }

    /**
     * Convenience for {@link Selection#getSelectionStart}.
     */
    public final int getSelectionStart() {
        return getView().getSelectionStart();
    }

    /**
     * Convenience for {@link Selection#getSelectionEnd}.
     */
    public final int getSelectionEnd() {
        return getView().getSelectionEnd();
    }

    /**
     * Return true iff there is a selection inside this text view.
     */
    public final boolean hasSelection() {
        return getView().hasSelection();
    }

    /**
     * Adds a TextWatcher to the list of those whose methods are called whenever this TextView's
     * text changes. <p> In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously not
     * called after {@link #setText} calls. Now, doing {@link #setText} if there are any text
     * changed listeners forces the buffer type to Editable if it would not otherwise be and does
     * call this method.
     */
    public final void addTextChangedListener(final TextWatcher watcher) {
        getView().addTextChangedListener(watcher);
    }

    /**
     * Removes the specified TextWatcher from the list of those whose methods are called whenever
     * this TextView's text changes.
     */
    public final void removeTextChangedListener(final TextWatcher watcher) {
        getView().removeTextChangedListener(watcher);
    }

    /**
     * Use {@link BaseInputConnection#removeComposingSpans BaseInputConnection.removeComposingSpans()}
     * to remove any IME composing state from this text view.
     */
    public final void clearComposingText() {
        getView().clearComposingText();
    }

    /**
     * Returns true, only while processing a touch gesture, if the initial touch down event caused
     * focus to move to the text view and as a result its selection changed. Only valid while
     * processing the touch gesture of interest, in an editable text view.
     */
    public final boolean didTouchFocusSelect() {
        return getView().didTouchFocusSelect();
    }

    public final void setScroller(final Scroller s) {
        getView().setScroller(s);
    }

    /**
     * Returns whether this text view is a current input method target. The default implementation
     * just checks with {@link InputMethodManager}.
     */
    public final boolean isInputMethodTarget() {
        return getView().isInputMethodTarget();
    }

    /**
     * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
     * by the IME or by the spell checker as the user types. This is done by adding {@link
     * SuggestionSpan}s to the text.
     * <p>
     * When suggestions are enabled (default), this list of suggestions will be displayed when the
     * user asks for them on these parts of the text. This value depends on the inputType of this
     * TextView.
     * <p>
     * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
     * <p>
     * In addition, the type variation must be one of {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
     * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
     * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
     * <p>
     * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be
     * set.
     *
     * @return true if the suggestions popup window is enabled, based on the inputType.
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public final boolean isSuggestionsEnabled() {
        return getView().isSuggestionsEnabled();
    }

    /**
     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
     * selection is initiated in this View.
     * <p>
     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
     * Paste actions, depending on what this View supports.
     * <p>
     * A custom implementation can add new entries in the default menu in its
     * android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu) method. The default
     * actions can also be removed from the menu using Menu#removeItem(int) and passing {@link
     * android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} or {@link
     * android.R.id#paste} ids as parameters.
     * <p>
     * Returning false from android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)
     * will prevent the action mode from being started.
     * <p>
     * Action click events should be handled by the custom implementation of
     * android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem) .
     * <p>
     * Note that text selection mode is not started when a TextView receives focus and the {@link
     * android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in that case,
     * to allow for quick replacement.
     */
    public final void setCustomSelectionActionModeCallback(
            final ActionMode.Callback actionModeCallback) {
        getView().setCustomSelectionActionModeCallback(actionModeCallback);
    }

    /**
     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
     *
     * @return The current custom selection callback.
     */
    public final ActionMode.Callback getCustomSelectionActionModeCallback() {
        return getView().getCustomSelectionActionModeCallback();
    }

    /**
     * Get the character offset closest to the specified absolute position. A typical use case is to
     * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
     *
     * @param x
     *         The horizontal absolute position of a point on screen
     * @param y
     *         The vertical absolute position of a point on screen
     * @return the character offset for the character whose position is closest to the specified
     * position. Returns -1 if there is no layout.
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public final int getOffsetForPosition(final float x, final float y) {
        return getView().getOffsetForPosition(x, y);
    }

    /**
     * Return the text the TextView is displaying. If setText() was called with an argument of
     * BufferType.SPANNABLE or BufferType.EDITABLE, you can cast the return value from this method
     * to Spannable or Editable, respectively.
     * <p>
     * Note: The content of the return value should not be modified. If you want a modifiable one,
     * you should make your own copy first.
     */
    public final Editable getText() {
        return getView().getText();
    }

    /**
     * Convenience for {@link Selection#setSelection(Spannable, int, int)}.
     */
    public final void setSelection(final int start, final int stop) {
        Selection.setSelection(getText(), start, stop);
    }

    /**
     * Convenience for {@link Selection#setSelection(Spannable, int)}.
     */
    public final void setSelection(final int index) {
        Selection.setSelection(getText(), index);
    }

    /**
     * Convenience for {@link Selection#selectAll}.
     */
    public final void selectAll() {
        Selection.selectAll(getText());
    }

    /**
     * Convenience for {@link Selection#extendSelection}.
     */
    public final void extendSelection(final int index) {
        Selection.extendSelection(getText(), index);
    }

    @Override
    protected final Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();

        if (superState != null) {
            SavedState savedState = new SavedState(superState);
            savedState.viewState = getView().onSaveInstanceState();
            savedState.maxNumberOfCharacters = getMaxNumberOfCharacters();
            return savedState;
        }

        return null;
    }

    @Override
    protected final void onRestoreInstanceState(final Parcelable state) {
        if (state != null && state instanceof SavedState) {
            SavedState savedState = (SavedState) state;
            validateOnValueChange(false);
            getView().onRestoreInstanceState(savedState.viewState);
            setMaxNumberOfCharacters(savedState.maxNumberOfCharacters);
            super.onRestoreInstanceState(savedState.getSuperState());
        } else {
            super.onRestoreInstanceState(state);
        }
    }

}