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