• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import com.android.internal.util.FastMath;
20 import com.android.internal.widget.EditableInputConnection;
21 
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.res.ColorStateList;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.content.res.XmlResourceParser;
30 import android.graphics.Canvas;
31 import android.graphics.Paint;
32 import android.graphics.Path;
33 import android.graphics.Rect;
34 import android.graphics.RectF;
35 import android.graphics.Typeface;
36 import android.graphics.drawable.Drawable;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.Message;
40 import android.os.Parcel;
41 import android.os.Parcelable;
42 import android.os.ResultReceiver;
43 import android.os.SystemClock;
44 import android.text.BoringLayout;
45 import android.text.ClipboardManager;
46 import android.text.DynamicLayout;
47 import android.text.Editable;
48 import android.text.GetChars;
49 import android.text.GraphicsOperations;
50 import android.text.InputFilter;
51 import android.text.InputType;
52 import android.text.Layout;
53 import android.text.ParcelableSpan;
54 import android.text.Selection;
55 import android.text.SpanWatcher;
56 import android.text.Spannable;
57 import android.text.SpannableString;
58 import android.text.Spanned;
59 import android.text.SpannedString;
60 import android.text.StaticLayout;
61 import android.text.TextPaint;
62 import android.text.TextUtils;
63 import android.text.TextWatcher;
64 import android.text.method.DateKeyListener;
65 import android.text.method.DateTimeKeyListener;
66 import android.text.method.DialerKeyListener;
67 import android.text.method.DigitsKeyListener;
68 import android.text.method.KeyListener;
69 import android.text.method.LinkMovementMethod;
70 import android.text.method.MetaKeyKeyListener;
71 import android.text.method.MovementMethod;
72 import android.text.method.PasswordTransformationMethod;
73 import android.text.method.SingleLineTransformationMethod;
74 import android.text.method.TextKeyListener;
75 import android.text.method.TimeKeyListener;
76 import android.text.method.TransformationMethod;
77 import android.text.style.ParagraphStyle;
78 import android.text.style.URLSpan;
79 import android.text.style.UpdateAppearance;
80 import android.text.util.Linkify;
81 import android.util.AttributeSet;
82 import android.util.FloatMath;
83 import android.util.Log;
84 import android.util.TypedValue;
85 import android.view.ContextMenu;
86 import android.view.Gravity;
87 import android.view.KeyEvent;
88 import android.view.LayoutInflater;
89 import android.view.MenuItem;
90 import android.view.MotionEvent;
91 import android.view.View;
92 import android.view.ViewDebug;
93 import android.view.ViewRoot;
94 import android.view.ViewTreeObserver;
95 import android.view.ViewGroup.LayoutParams;
96 import android.view.accessibility.AccessibilityEvent;
97 import android.view.accessibility.AccessibilityManager;
98 import android.view.animation.AnimationUtils;
99 import android.view.inputmethod.BaseInputConnection;
100 import android.view.inputmethod.CompletionInfo;
101 import android.view.inputmethod.EditorInfo;
102 import android.view.inputmethod.ExtractedText;
103 import android.view.inputmethod.ExtractedTextRequest;
104 import android.view.inputmethod.InputConnection;
105 import android.view.inputmethod.InputMethodManager;
106 import android.widget.RemoteViews.RemoteView;
107 
108 import java.io.IOException;
109 import java.lang.ref.WeakReference;
110 import java.util.ArrayList;
111 
112 /**
113  * Displays text to the user and optionally allows them to edit it.  A TextView
114  * is a complete text editor, however the basic class is configured to not
115  * allow editing; see {@link EditText} for a subclass that configures the text
116  * view for editing.
117  *
118  * <p>
119  * <b>XML attributes</b>
120  * <p>
121  * See {@link android.R.styleable#TextView TextView Attributes},
122  * {@link android.R.styleable#View View Attributes}
123  *
124  * @attr ref android.R.styleable#TextView_text
125  * @attr ref android.R.styleable#TextView_bufferType
126  * @attr ref android.R.styleable#TextView_hint
127  * @attr ref android.R.styleable#TextView_textColor
128  * @attr ref android.R.styleable#TextView_textColorHighlight
129  * @attr ref android.R.styleable#TextView_textColorHint
130  * @attr ref android.R.styleable#TextView_textAppearance
131  * @attr ref android.R.styleable#TextView_textColorLink
132  * @attr ref android.R.styleable#TextView_textSize
133  * @attr ref android.R.styleable#TextView_textScaleX
134  * @attr ref android.R.styleable#TextView_typeface
135  * @attr ref android.R.styleable#TextView_textStyle
136  * @attr ref android.R.styleable#TextView_cursorVisible
137  * @attr ref android.R.styleable#TextView_maxLines
138  * @attr ref android.R.styleable#TextView_maxHeight
139  * @attr ref android.R.styleable#TextView_lines
140  * @attr ref android.R.styleable#TextView_height
141  * @attr ref android.R.styleable#TextView_minLines
142  * @attr ref android.R.styleable#TextView_minHeight
143  * @attr ref android.R.styleable#TextView_maxEms
144  * @attr ref android.R.styleable#TextView_maxWidth
145  * @attr ref android.R.styleable#TextView_ems
146  * @attr ref android.R.styleable#TextView_width
147  * @attr ref android.R.styleable#TextView_minEms
148  * @attr ref android.R.styleable#TextView_minWidth
149  * @attr ref android.R.styleable#TextView_gravity
150  * @attr ref android.R.styleable#TextView_scrollHorizontally
151  * @attr ref android.R.styleable#TextView_password
152  * @attr ref android.R.styleable#TextView_singleLine
153  * @attr ref android.R.styleable#TextView_selectAllOnFocus
154  * @attr ref android.R.styleable#TextView_includeFontPadding
155  * @attr ref android.R.styleable#TextView_maxLength
156  * @attr ref android.R.styleable#TextView_shadowColor
157  * @attr ref android.R.styleable#TextView_shadowDx
158  * @attr ref android.R.styleable#TextView_shadowDy
159  * @attr ref android.R.styleable#TextView_shadowRadius
160  * @attr ref android.R.styleable#TextView_autoLink
161  * @attr ref android.R.styleable#TextView_linksClickable
162  * @attr ref android.R.styleable#TextView_numeric
163  * @attr ref android.R.styleable#TextView_digits
164  * @attr ref android.R.styleable#TextView_phoneNumber
165  * @attr ref android.R.styleable#TextView_inputMethod
166  * @attr ref android.R.styleable#TextView_capitalize
167  * @attr ref android.R.styleable#TextView_autoText
168  * @attr ref android.R.styleable#TextView_editable
169  * @attr ref android.R.styleable#TextView_freezesText
170  * @attr ref android.R.styleable#TextView_ellipsize
171  * @attr ref android.R.styleable#TextView_drawableTop
172  * @attr ref android.R.styleable#TextView_drawableBottom
173  * @attr ref android.R.styleable#TextView_drawableRight
174  * @attr ref android.R.styleable#TextView_drawableLeft
175  * @attr ref android.R.styleable#TextView_drawablePadding
176  * @attr ref android.R.styleable#TextView_lineSpacingExtra
177  * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
178  * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
179  * @attr ref android.R.styleable#TextView_inputType
180  * @attr ref android.R.styleable#TextView_imeOptions
181  * @attr ref android.R.styleable#TextView_privateImeOptions
182  * @attr ref android.R.styleable#TextView_imeActionLabel
183  * @attr ref android.R.styleable#TextView_imeActionId
184  * @attr ref android.R.styleable#TextView_editorExtras
185  */
186 @RemoteView
187 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
188     static final String TAG = "TextView";
189     static final boolean DEBUG_EXTRACT = false;
190 
191     private static int PRIORITY = 100;
192 
193     private ColorStateList mTextColor;
194     private int mCurTextColor;
195     private ColorStateList mHintTextColor;
196     private ColorStateList mLinkTextColor;
197     private int mCurHintTextColor;
198     private boolean mFreezesText;
199     private boolean mFrozenWithFocus;
200     private boolean mTemporaryDetach;
201 
202     private boolean mEatTouchRelease = false;
203     private boolean mScrolled = false;
204 
205     private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
206     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
207 
208     private float mShadowRadius, mShadowDx, mShadowDy;
209 
210     private static final int PREDRAW_NOT_REGISTERED = 0;
211     private static final int PREDRAW_PENDING = 1;
212     private static final int PREDRAW_DONE = 2;
213     private int mPreDrawState = PREDRAW_NOT_REGISTERED;
214 
215     private TextUtils.TruncateAt mEllipsize = null;
216 
217     // Enum for the "typeface" XML parameter.
218     // TODO: How can we get this from the XML instead of hardcoding it here?
219     private static final int SANS = 1;
220     private static final int SERIF = 2;
221     private static final int MONOSPACE = 3;
222 
223     // Bitfield for the "numeric" XML parameter.
224     // TODO: How can we get this from the XML instead of hardcoding it here?
225     private static final int SIGNED = 2;
226     private static final int DECIMAL = 4;
227 
228     class Drawables {
229         final Rect mCompoundRect = new Rect();
230         Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
231         int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
232         int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
233         int mDrawablePadding;
234     }
235     private Drawables mDrawables;
236 
237     private CharSequence mError;
238     private boolean mErrorWasChanged;
239     private ErrorPopup mPopup;
240     /**
241      * This flag is set if the TextView tries to display an error before it
242      * is attached to the window (so its position is still unknown).
243      * It causes the error to be shown later, when onAttachedToWindow()
244      * is called.
245      */
246     private boolean mShowErrorAfterAttach;
247 
248     private CharWrapper mCharWrapper = null;
249 
250     private boolean mSelectionMoved = false;
251     private boolean mTouchFocusSelected = false;
252 
253     private Marquee mMarquee;
254     private boolean mRestartMarquee;
255 
256     private int mMarqueeRepeatLimit = 3;
257 
258     class InputContentType {
259         int imeOptions = EditorInfo.IME_NULL;
260         String privateImeOptions;
261         CharSequence imeActionLabel;
262         int imeActionId;
263         Bundle extras;
264         OnEditorActionListener onEditorActionListener;
265         boolean enterDown;
266     }
267     InputContentType mInputContentType;
268 
269     class InputMethodState {
270         Rect mCursorRectInWindow = new Rect();
271         RectF mTmpRectF = new RectF();
272         float[] mTmpOffset = new float[2];
273         ExtractedTextRequest mExtracting;
274         final ExtractedText mTmpExtracted = new ExtractedText();
275         int mBatchEditNesting;
276         boolean mCursorChanged;
277         boolean mSelectionModeChanged;
278         boolean mContentChanged;
279         int mChangedStart, mChangedEnd, mChangedDelta;
280     }
281     InputMethodState mInputMethodState;
282 
283     /*
284      * Kick-start the font cache for the zygote process (to pay the cost of
285      * initializing freetype for our default font only once).
286      */
287     static {
288         Paint p = new Paint();
289         p.setAntiAlias(true);
290         // We don't care about the result, just the side-effect of measuring.
291         p.measureText("H");
292     }
293 
294     /**
295      * Interface definition for a callback to be invoked when an action is
296      * performed on the editor.
297      */
298     public interface OnEditorActionListener {
299         /**
300          * Called when an action is being performed.
301          *
302          * @param v The view that was clicked.
303          * @param actionId Identifier of the action.  This will be either the
304          * identifier you supplied, or {@link EditorInfo#IME_NULL
305          * EditorInfo.IME_NULL} if being called due to the enter key
306          * being pressed.
307          * @param event If triggered by an enter key, this is the event;
308          * otherwise, this is null.
309          * @return Return true if you have consumed the action, else false.
310          */
onEditorAction(TextView v, int actionId, KeyEvent event)311         boolean onEditorAction(TextView v, int actionId, KeyEvent event);
312     }
313 
TextView(Context context)314     public TextView(Context context) {
315         this(context, null);
316     }
317 
TextView(Context context, AttributeSet attrs)318     public TextView(Context context,
319                     AttributeSet attrs) {
320         this(context, attrs, com.android.internal.R.attr.textViewStyle);
321     }
322 
TextView(Context context, AttributeSet attrs, int defStyle)323     public TextView(Context context,
324                     AttributeSet attrs,
325                     int defStyle) {
326         super(context, attrs, defStyle);
327         mText = "";
328 
329         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
330         mTextPaint.density = getResources().getDisplayMetrics().density;
331         mTextPaint.setCompatibilityScaling(
332                 getResources().getCompatibilityInfo().applicationScale);
333 
334         // If we get the paint from the skin, we should set it to left, since
335         // the layout always wants it to be left.
336         // mTextPaint.setTextAlign(Paint.Align.LEFT);
337 
338         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
339         mHighlightPaint.setCompatibilityScaling(
340                 getResources().getCompatibilityInfo().applicationScale);
341 
342         mMovement = getDefaultMovementMethod();
343         mTransformation = null;
344 
345         TypedArray a =
346             context.obtainStyledAttributes(
347                 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
348 
349         int textColorHighlight = 0;
350         ColorStateList textColor = null;
351         ColorStateList textColorHint = null;
352         ColorStateList textColorLink = null;
353         int textSize = 15;
354         int typefaceIndex = -1;
355         int styleIndex = -1;
356 
357         /*
358          * Look the appearance up without checking first if it exists because
359          * almost every TextView has one and it greatly simplifies the logic
360          * to be able to parse the appearance first and then let specific tags
361          * for this View override it.
362          */
363         TypedArray appearance = null;
364         int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
365         if (ap != -1) {
366             appearance = context.obtainStyledAttributes(ap,
367                                 com.android.internal.R.styleable.
368                                 TextAppearance);
369         }
370         if (appearance != null) {
371             int n = appearance.getIndexCount();
372             for (int i = 0; i < n; i++) {
373                 int attr = appearance.getIndex(i);
374 
375                 switch (attr) {
376                 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
377                     textColorHighlight = appearance.getColor(attr, textColorHighlight);
378                     break;
379 
380                 case com.android.internal.R.styleable.TextAppearance_textColor:
381                     textColor = appearance.getColorStateList(attr);
382                     break;
383 
384                 case com.android.internal.R.styleable.TextAppearance_textColorHint:
385                     textColorHint = appearance.getColorStateList(attr);
386                     break;
387 
388                 case com.android.internal.R.styleable.TextAppearance_textColorLink:
389                     textColorLink = appearance.getColorStateList(attr);
390                     break;
391 
392                 case com.android.internal.R.styleable.TextAppearance_textSize:
393                     textSize = appearance.getDimensionPixelSize(attr, textSize);
394                     break;
395 
396                 case com.android.internal.R.styleable.TextAppearance_typeface:
397                     typefaceIndex = appearance.getInt(attr, -1);
398                     break;
399 
400                 case com.android.internal.R.styleable.TextAppearance_textStyle:
401                     styleIndex = appearance.getInt(attr, -1);
402                     break;
403                 }
404             }
405 
406             appearance.recycle();
407         }
408 
409         boolean editable = getDefaultEditable();
410         CharSequence inputMethod = null;
411         int numeric = 0;
412         CharSequence digits = null;
413         boolean phone = false;
414         boolean autotext = false;
415         int autocap = -1;
416         int buffertype = 0;
417         boolean selectallonfocus = false;
418         Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
419             drawableBottom = null;
420         int drawablePadding = 0;
421         int ellipsize = -1;
422         boolean singleLine = false;
423         int maxlength = -1;
424         CharSequence text = "";
425         CharSequence hint = null;
426         int shadowcolor = 0;
427         float dx = 0, dy = 0, r = 0;
428         boolean password = false;
429         int inputType = EditorInfo.TYPE_NULL;
430 
431         int n = a.getIndexCount();
432         for (int i = 0; i < n; i++) {
433             int attr = a.getIndex(i);
434 
435             switch (attr) {
436             case com.android.internal.R.styleable.TextView_editable:
437                 editable = a.getBoolean(attr, editable);
438                 break;
439 
440             case com.android.internal.R.styleable.TextView_inputMethod:
441                 inputMethod = a.getText(attr);
442                 break;
443 
444             case com.android.internal.R.styleable.TextView_numeric:
445                 numeric = a.getInt(attr, numeric);
446                 break;
447 
448             case com.android.internal.R.styleable.TextView_digits:
449                 digits = a.getText(attr);
450                 break;
451 
452             case com.android.internal.R.styleable.TextView_phoneNumber:
453                 phone = a.getBoolean(attr, phone);
454                 break;
455 
456             case com.android.internal.R.styleable.TextView_autoText:
457                 autotext = a.getBoolean(attr, autotext);
458                 break;
459 
460             case com.android.internal.R.styleable.TextView_capitalize:
461                 autocap = a.getInt(attr, autocap);
462                 break;
463 
464             case com.android.internal.R.styleable.TextView_bufferType:
465                 buffertype = a.getInt(attr, buffertype);
466                 break;
467 
468             case com.android.internal.R.styleable.TextView_selectAllOnFocus:
469                 selectallonfocus = a.getBoolean(attr, selectallonfocus);
470                 break;
471 
472             case com.android.internal.R.styleable.TextView_autoLink:
473                 mAutoLinkMask = a.getInt(attr, 0);
474                 break;
475 
476             case com.android.internal.R.styleable.TextView_linksClickable:
477                 mLinksClickable = a.getBoolean(attr, true);
478                 break;
479 
480             case com.android.internal.R.styleable.TextView_drawableLeft:
481                 drawableLeft = a.getDrawable(attr);
482                 break;
483 
484             case com.android.internal.R.styleable.TextView_drawableTop:
485                 drawableTop = a.getDrawable(attr);
486                 break;
487 
488             case com.android.internal.R.styleable.TextView_drawableRight:
489                 drawableRight = a.getDrawable(attr);
490                 break;
491 
492             case com.android.internal.R.styleable.TextView_drawableBottom:
493                 drawableBottom = a.getDrawable(attr);
494                 break;
495 
496             case com.android.internal.R.styleable.TextView_drawablePadding:
497                 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
498                 break;
499 
500             case com.android.internal.R.styleable.TextView_maxLines:
501                 setMaxLines(a.getInt(attr, -1));
502                 break;
503 
504             case com.android.internal.R.styleable.TextView_maxHeight:
505                 setMaxHeight(a.getDimensionPixelSize(attr, -1));
506                 break;
507 
508             case com.android.internal.R.styleable.TextView_lines:
509                 setLines(a.getInt(attr, -1));
510                 break;
511 
512             case com.android.internal.R.styleable.TextView_height:
513                 setHeight(a.getDimensionPixelSize(attr, -1));
514                 break;
515 
516             case com.android.internal.R.styleable.TextView_minLines:
517                 setMinLines(a.getInt(attr, -1));
518                 break;
519 
520             case com.android.internal.R.styleable.TextView_minHeight:
521                 setMinHeight(a.getDimensionPixelSize(attr, -1));
522                 break;
523 
524             case com.android.internal.R.styleable.TextView_maxEms:
525                 setMaxEms(a.getInt(attr, -1));
526                 break;
527 
528             case com.android.internal.R.styleable.TextView_maxWidth:
529                 setMaxWidth(a.getDimensionPixelSize(attr, -1));
530                 break;
531 
532             case com.android.internal.R.styleable.TextView_ems:
533                 setEms(a.getInt(attr, -1));
534                 break;
535 
536             case com.android.internal.R.styleable.TextView_width:
537                 setWidth(a.getDimensionPixelSize(attr, -1));
538                 break;
539 
540             case com.android.internal.R.styleable.TextView_minEms:
541                 setMinEms(a.getInt(attr, -1));
542                 break;
543 
544             case com.android.internal.R.styleable.TextView_minWidth:
545                 setMinWidth(a.getDimensionPixelSize(attr, -1));
546                 break;
547 
548             case com.android.internal.R.styleable.TextView_gravity:
549                 setGravity(a.getInt(attr, -1));
550                 break;
551 
552             case com.android.internal.R.styleable.TextView_hint:
553                 hint = a.getText(attr);
554                 break;
555 
556             case com.android.internal.R.styleable.TextView_text:
557                 text = a.getText(attr);
558                 break;
559 
560             case com.android.internal.R.styleable.TextView_scrollHorizontally:
561                 if (a.getBoolean(attr, false)) {
562                     setHorizontallyScrolling(true);
563                 }
564                 break;
565 
566             case com.android.internal.R.styleable.TextView_singleLine:
567                 singleLine = a.getBoolean(attr, singleLine);
568                 break;
569 
570             case com.android.internal.R.styleable.TextView_ellipsize:
571                 ellipsize = a.getInt(attr, ellipsize);
572                 break;
573 
574             case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
575                 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
576                 break;
577 
578             case com.android.internal.R.styleable.TextView_includeFontPadding:
579                 if (!a.getBoolean(attr, true)) {
580                     setIncludeFontPadding(false);
581                 }
582                 break;
583 
584             case com.android.internal.R.styleable.TextView_cursorVisible:
585                 if (!a.getBoolean(attr, true)) {
586                     setCursorVisible(false);
587                 }
588                 break;
589 
590             case com.android.internal.R.styleable.TextView_maxLength:
591                 maxlength = a.getInt(attr, -1);
592                 break;
593 
594             case com.android.internal.R.styleable.TextView_textScaleX:
595                 setTextScaleX(a.getFloat(attr, 1.0f));
596                 break;
597 
598             case com.android.internal.R.styleable.TextView_freezesText:
599                 mFreezesText = a.getBoolean(attr, false);
600                 break;
601 
602             case com.android.internal.R.styleable.TextView_shadowColor:
603                 shadowcolor = a.getInt(attr, 0);
604                 break;
605 
606             case com.android.internal.R.styleable.TextView_shadowDx:
607                 dx = a.getFloat(attr, 0);
608                 break;
609 
610             case com.android.internal.R.styleable.TextView_shadowDy:
611                 dy = a.getFloat(attr, 0);
612                 break;
613 
614             case com.android.internal.R.styleable.TextView_shadowRadius:
615                 r = a.getFloat(attr, 0);
616                 break;
617 
618             case com.android.internal.R.styleable.TextView_enabled:
619                 setEnabled(a.getBoolean(attr, isEnabled()));
620                 break;
621 
622             case com.android.internal.R.styleable.TextView_textColorHighlight:
623                 textColorHighlight = a.getColor(attr, textColorHighlight);
624                 break;
625 
626             case com.android.internal.R.styleable.TextView_textColor:
627                 textColor = a.getColorStateList(attr);
628                 break;
629 
630             case com.android.internal.R.styleable.TextView_textColorHint:
631                 textColorHint = a.getColorStateList(attr);
632                 break;
633 
634             case com.android.internal.R.styleable.TextView_textColorLink:
635                 textColorLink = a.getColorStateList(attr);
636                 break;
637 
638             case com.android.internal.R.styleable.TextView_textSize:
639                 textSize = a.getDimensionPixelSize(attr, textSize);
640                 break;
641 
642             case com.android.internal.R.styleable.TextView_typeface:
643                 typefaceIndex = a.getInt(attr, typefaceIndex);
644                 break;
645 
646             case com.android.internal.R.styleable.TextView_textStyle:
647                 styleIndex = a.getInt(attr, styleIndex);
648                 break;
649 
650             case com.android.internal.R.styleable.TextView_password:
651                 password = a.getBoolean(attr, password);
652                 break;
653 
654             case com.android.internal.R.styleable.TextView_lineSpacingExtra:
655                 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
656                 break;
657 
658             case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
659                 mSpacingMult = a.getFloat(attr, mSpacingMult);
660                 break;
661 
662             case com.android.internal.R.styleable.TextView_inputType:
663                 inputType = a.getInt(attr, mInputType);
664                 break;
665 
666             case com.android.internal.R.styleable.TextView_imeOptions:
667                 if (mInputContentType == null) {
668                     mInputContentType = new InputContentType();
669                 }
670                 mInputContentType.imeOptions = a.getInt(attr,
671                         mInputContentType.imeOptions);
672                 break;
673 
674             case com.android.internal.R.styleable.TextView_imeActionLabel:
675                 if (mInputContentType == null) {
676                     mInputContentType = new InputContentType();
677                 }
678                 mInputContentType.imeActionLabel = a.getText(attr);
679                 break;
680 
681             case com.android.internal.R.styleable.TextView_imeActionId:
682                 if (mInputContentType == null) {
683                     mInputContentType = new InputContentType();
684                 }
685                 mInputContentType.imeActionId = a.getInt(attr,
686                         mInputContentType.imeActionId);
687                 break;
688 
689             case com.android.internal.R.styleable.TextView_privateImeOptions:
690                 setPrivateImeOptions(a.getString(attr));
691                 break;
692 
693             case com.android.internal.R.styleable.TextView_editorExtras:
694                 try {
695                     setInputExtras(a.getResourceId(attr, 0));
696                 } catch (XmlPullParserException e) {
697                     Log.w("TextView", "Failure reading input extras", e);
698                 } catch (IOException e) {
699                     Log.w("TextView", "Failure reading input extras", e);
700                 }
701                 break;
702             }
703         }
704         a.recycle();
705 
706         BufferType bufferType = BufferType.EDITABLE;
707 
708         if ((inputType&(EditorInfo.TYPE_MASK_CLASS
709                 |EditorInfo.TYPE_MASK_VARIATION))
710                 == (EditorInfo.TYPE_CLASS_TEXT
711                         |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
712             password = true;
713         }
714 
715         if (inputMethod != null) {
716             Class c;
717 
718             try {
719                 c = Class.forName(inputMethod.toString());
720             } catch (ClassNotFoundException ex) {
721                 throw new RuntimeException(ex);
722             }
723 
724             try {
725                 mInput = (KeyListener) c.newInstance();
726             } catch (InstantiationException ex) {
727                 throw new RuntimeException(ex);
728             } catch (IllegalAccessException ex) {
729                 throw new RuntimeException(ex);
730             }
731             try {
732                 mInputType = inputType != EditorInfo.TYPE_NULL
733                         ? inputType
734                         : mInput.getInputType();
735             } catch (IncompatibleClassChangeError e) {
736                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
737             }
738         } else if (digits != null) {
739             mInput = DigitsKeyListener.getInstance(digits.toString());
740             // If no input type was specified, we will default to generic
741             // text, since we can't tell the IME about the set of digits
742             // that was selected.
743             mInputType = inputType != EditorInfo.TYPE_NULL
744                     ? inputType : EditorInfo.TYPE_CLASS_TEXT;
745         } else if (inputType != EditorInfo.TYPE_NULL) {
746             setInputType(inputType, true);
747             singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
748                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
749                     (EditorInfo.TYPE_CLASS_TEXT
750                             | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
751         } else if (phone) {
752             mInput = DialerKeyListener.getInstance();
753             mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
754         } else if (numeric != 0) {
755             mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
756                                                    (numeric & DECIMAL) != 0);
757             inputType = EditorInfo.TYPE_CLASS_NUMBER;
758             if ((numeric & SIGNED) != 0) {
759                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
760             }
761             if ((numeric & DECIMAL) != 0) {
762                 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
763             }
764             mInputType = inputType;
765         } else if (autotext || autocap != -1) {
766             TextKeyListener.Capitalize cap;
767 
768             inputType = EditorInfo.TYPE_CLASS_TEXT;
769             if (!singleLine) {
770                 inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
771             }
772 
773             switch (autocap) {
774             case 1:
775                 cap = TextKeyListener.Capitalize.SENTENCES;
776                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
777                 break;
778 
779             case 2:
780                 cap = TextKeyListener.Capitalize.WORDS;
781                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
782                 break;
783 
784             case 3:
785                 cap = TextKeyListener.Capitalize.CHARACTERS;
786                 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
787                 break;
788 
789             default:
790                 cap = TextKeyListener.Capitalize.NONE;
791                 break;
792             }
793 
794             mInput = TextKeyListener.getInstance(autotext, cap);
795             mInputType = inputType;
796         } else if (editable) {
797             mInput = TextKeyListener.getInstance();
798             mInputType = EditorInfo.TYPE_CLASS_TEXT;
799         } else {
800             mInput = null;
801 
802             switch (buffertype) {
803                 case 0:
804                     bufferType = BufferType.NORMAL;
805                     break;
806                 case 1:
807                     bufferType = BufferType.SPANNABLE;
808                     break;
809                 case 2:
810                     bufferType = BufferType.EDITABLE;
811                     break;
812             }
813         }
814 
815         if (password && (mInputType&EditorInfo.TYPE_MASK_CLASS)
816                 == EditorInfo.TYPE_CLASS_TEXT) {
817             mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
818                 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
819         }
820 
821         if (selectallonfocus) {
822             mSelectAllOnFocus = true;
823 
824             if (bufferType == BufferType.NORMAL)
825                 bufferType = BufferType.SPANNABLE;
826         }
827 
828         setCompoundDrawablesWithIntrinsicBounds(
829             drawableLeft, drawableTop, drawableRight, drawableBottom);
830         setCompoundDrawablePadding(drawablePadding);
831 
832         if (singleLine) {
833             setSingleLine();
834 
835             if (mInput == null && ellipsize < 0) {
836                 ellipsize = 3; // END
837             }
838         }
839 
840         switch (ellipsize) {
841             case 1:
842                 setEllipsize(TextUtils.TruncateAt.START);
843                 break;
844             case 2:
845                 setEllipsize(TextUtils.TruncateAt.MIDDLE);
846                 break;
847             case 3:
848                 setEllipsize(TextUtils.TruncateAt.END);
849                 break;
850             case 4:
851                 setHorizontalFadingEdgeEnabled(true);
852                 setEllipsize(TextUtils.TruncateAt.MARQUEE);
853                 break;
854         }
855 
856         setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
857         setHintTextColor(textColorHint);
858         setLinkTextColor(textColorLink);
859         if (textColorHighlight != 0) {
860             setHighlightColor(textColorHighlight);
861         }
862         setRawTextSize(textSize);
863 
864         if (password) {
865             setTransformationMethod(PasswordTransformationMethod.getInstance());
866             typefaceIndex = MONOSPACE;
867         } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
868                 |EditorInfo.TYPE_MASK_VARIATION))
869                 == (EditorInfo.TYPE_CLASS_TEXT
870                         |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
871             typefaceIndex = MONOSPACE;
872         }
873 
874         setTypefaceByIndex(typefaceIndex, styleIndex);
875 
876         if (shadowcolor != 0) {
877             setShadowLayer(r, dx, dy, shadowcolor);
878         }
879 
880         if (maxlength >= 0) {
881             setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
882         } else {
883             setFilters(NO_FILTERS);
884         }
885 
886         setText(text, bufferType);
887         if (hint != null) setHint(hint);
888 
889         /*
890          * Views are not normally focusable unless specified to be.
891          * However, TextViews that have input or movement methods *are*
892          * focusable by default.
893          */
894         a = context.obtainStyledAttributes(attrs,
895                                            com.android.internal.R.styleable.View,
896                                            defStyle, 0);
897 
898         boolean focusable = mMovement != null || mInput != null;
899         boolean clickable = focusable;
900         boolean longClickable = focusable;
901 
902         n = a.getIndexCount();
903         for (int i = 0; i < n; i++) {
904             int attr = a.getIndex(i);
905 
906             switch (attr) {
907             case com.android.internal.R.styleable.View_focusable:
908                 focusable = a.getBoolean(attr, focusable);
909                 break;
910 
911             case com.android.internal.R.styleable.View_clickable:
912                 clickable = a.getBoolean(attr, clickable);
913                 break;
914 
915             case com.android.internal.R.styleable.View_longClickable:
916                 longClickable = a.getBoolean(attr, longClickable);
917                 break;
918             }
919         }
920         a.recycle();
921 
922         setFocusable(focusable);
923         setClickable(clickable);
924         setLongClickable(longClickable);
925     }
926 
setTypefaceByIndex(int typefaceIndex, int styleIndex)927     private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
928         Typeface tf = null;
929         switch (typefaceIndex) {
930             case SANS:
931                 tf = Typeface.SANS_SERIF;
932                 break;
933 
934             case SERIF:
935                 tf = Typeface.SERIF;
936                 break;
937 
938             case MONOSPACE:
939                 tf = Typeface.MONOSPACE;
940                 break;
941         }
942 
943         setTypeface(tf, styleIndex);
944     }
945 
946     /**
947      * Sets the typeface and style in which the text should be displayed,
948      * and turns on the fake bold and italic bits in the Paint if the
949      * Typeface that you provided does not have all the bits in the
950      * style that you specified.
951      *
952      * @attr ref android.R.styleable#TextView_typeface
953      * @attr ref android.R.styleable#TextView_textStyle
954      */
setTypeface(Typeface tf, int style)955     public void setTypeface(Typeface tf, int style) {
956         if (style > 0) {
957             if (tf == null) {
958                 tf = Typeface.defaultFromStyle(style);
959             } else {
960                 tf = Typeface.create(tf, style);
961             }
962 
963             setTypeface(tf);
964             // now compute what (if any) algorithmic styling is needed
965             int typefaceStyle = tf != null ? tf.getStyle() : 0;
966             int need = style & ~typefaceStyle;
967             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
968             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
969         } else {
970             mTextPaint.setFakeBoldText(false);
971             mTextPaint.setTextSkewX(0);
972             setTypeface(tf);
973         }
974     }
975 
976     /**
977      * Subclasses override this to specify that they have a KeyListener
978      * by default even if not specifically called for in the XML options.
979      */
getDefaultEditable()980     protected boolean getDefaultEditable() {
981         return false;
982     }
983 
984     /**
985      * Subclasses override this to specify a default movement method.
986      */
getDefaultMovementMethod()987     protected MovementMethod getDefaultMovementMethod() {
988         return null;
989     }
990 
991     /**
992      * Return the text the TextView is displaying. If setText() was called with
993      * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
994      * the return value from this method to Spannable or Editable, respectively.
995      *
996      * Note: The content of the return value should not be modified. If you want
997      * a modifiable one, you should make your own copy first.
998      */
999     @ViewDebug.CapturedViewProperty
getText()1000     public CharSequence getText() {
1001         return mText;
1002     }
1003 
1004     /**
1005      * Returns the length, in characters, of the text managed by this TextView
1006      */
length()1007     public int length() {
1008         return mText.length();
1009     }
1010 
1011     /**
1012      * Return the text the TextView is displaying as an Editable object.  If
1013      * the text is not editable, null is returned.
1014      *
1015      * @see #getText
1016      */
getEditableText()1017     public Editable getEditableText() {
1018         return (mText instanceof Editable) ? (Editable)mText : null;
1019     }
1020 
1021     /**
1022      * @return the height of one standard line in pixels.  Note that markup
1023      * within the text can cause individual lines to be taller or shorter
1024      * than this height, and the layout may contain additional first-
1025      * or last-line padding.
1026      */
getLineHeight()1027     public int getLineHeight() {
1028         return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult
1029                           + mSpacingAdd);
1030     }
1031 
1032     /**
1033      * @return the Layout that is currently being used to display the text.
1034      * This can be null if the text or width has recently changes.
1035      */
getLayout()1036     public final Layout getLayout() {
1037         return mLayout;
1038     }
1039 
1040     /**
1041      * @return the current key listener for this TextView.
1042      * This will frequently be null for non-EditText TextViews.
1043      */
getKeyListener()1044     public final KeyListener getKeyListener() {
1045         return mInput;
1046     }
1047 
1048     /**
1049      * Sets the key listener to be used with this TextView.  This can be null
1050      * to disallow user input.  Note that this method has significant and
1051      * subtle interactions with soft keyboards and other input method:
1052      * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1053      * for important details.  Calling this method will replace the current
1054      * content type of the text view with the content type returned by the
1055      * key listener.
1056      * <p>
1057      * Be warned that if you want a TextView with a key listener or movement
1058      * method not to be focusable, or if you want a TextView without a
1059      * key listener or movement method to be focusable, you must call
1060      * {@link #setFocusable} again after calling this to get the focusability
1061      * back the way you want it.
1062      *
1063      * @attr ref android.R.styleable#TextView_numeric
1064      * @attr ref android.R.styleable#TextView_digits
1065      * @attr ref android.R.styleable#TextView_phoneNumber
1066      * @attr ref android.R.styleable#TextView_inputMethod
1067      * @attr ref android.R.styleable#TextView_capitalize
1068      * @attr ref android.R.styleable#TextView_autoText
1069      */
setKeyListener(KeyListener input)1070     public void setKeyListener(KeyListener input) {
1071         setKeyListenerOnly(input);
1072         fixFocusableAndClickableSettings();
1073 
1074         if (input != null) {
1075             try {
1076                 mInputType = mInput.getInputType();
1077             } catch (IncompatibleClassChangeError e) {
1078                 mInputType = EditorInfo.TYPE_CLASS_TEXT;
1079             }
1080             if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
1081                     == EditorInfo.TYPE_CLASS_TEXT) {
1082                 if (mSingleLine) {
1083                     mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1084                 } else {
1085                     mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
1086                 }
1087             }
1088         } else {
1089             mInputType = EditorInfo.TYPE_NULL;
1090         }
1091 
1092         InputMethodManager imm = InputMethodManager.peekInstance();
1093         if (imm != null) imm.restartInput(this);
1094     }
1095 
setKeyListenerOnly(KeyListener input)1096     private void setKeyListenerOnly(KeyListener input) {
1097         mInput = input;
1098         if (mInput != null && !(mText instanceof Editable))
1099             setText(mText);
1100 
1101         setFilters((Editable) mText, mFilters);
1102     }
1103 
1104     /**
1105      * @return the movement method being used for this TextView.
1106      * This will frequently be null for non-EditText TextViews.
1107      */
getMovementMethod()1108     public final MovementMethod getMovementMethod() {
1109         return mMovement;
1110     }
1111 
1112     /**
1113      * Sets the movement method (arrow key handler) to be used for
1114      * this TextView.  This can be null to disallow using the arrow keys
1115      * to move the cursor or scroll the view.
1116      * <p>
1117      * Be warned that if you want a TextView with a key listener or movement
1118      * method not to be focusable, or if you want a TextView without a
1119      * key listener or movement method to be focusable, you must call
1120      * {@link #setFocusable} again after calling this to get the focusability
1121      * back the way you want it.
1122      */
setMovementMethod(MovementMethod movement)1123     public final void setMovementMethod(MovementMethod movement) {
1124         mMovement = movement;
1125 
1126         if (mMovement != null && !(mText instanceof Spannable))
1127             setText(mText);
1128 
1129         fixFocusableAndClickableSettings();
1130     }
1131 
fixFocusableAndClickableSettings()1132     private void fixFocusableAndClickableSettings() {
1133         if ((mMovement != null) || mInput != null) {
1134             setFocusable(true);
1135             setClickable(true);
1136             setLongClickable(true);
1137         } else {
1138             setFocusable(false);
1139             setClickable(false);
1140             setLongClickable(false);
1141         }
1142     }
1143 
1144     /**
1145      * @return the current transformation method for this TextView.
1146      * This will frequently be null except for single-line and password
1147      * fields.
1148      */
getTransformationMethod()1149     public final TransformationMethod getTransformationMethod() {
1150         return mTransformation;
1151     }
1152 
1153     /**
1154      * Sets the transformation that is applied to the text that this
1155      * TextView is displaying.
1156      *
1157      * @attr ref android.R.styleable#TextView_password
1158      * @attr ref android.R.styleable#TextView_singleLine
1159      */
setTransformationMethod(TransformationMethod method)1160     public final void setTransformationMethod(TransformationMethod method) {
1161         if (method == mTransformation) {
1162             // Avoid the setText() below if the transformation is
1163             // the same.
1164             return;
1165         }
1166         if (mTransformation != null) {
1167             if (mText instanceof Spannable) {
1168                 ((Spannable) mText).removeSpan(mTransformation);
1169             }
1170         }
1171 
1172         mTransformation = method;
1173 
1174         setText(mText);
1175     }
1176 
1177     /**
1178      * Returns the top padding of the view, plus space for the top
1179      * Drawable if any.
1180      */
getCompoundPaddingTop()1181     public int getCompoundPaddingTop() {
1182         final Drawables dr = mDrawables;
1183         if (dr == null || dr.mDrawableTop == null) {
1184             return mPaddingTop;
1185         } else {
1186             return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1187         }
1188     }
1189 
1190     /**
1191      * Returns the bottom padding of the view, plus space for the bottom
1192      * Drawable if any.
1193      */
getCompoundPaddingBottom()1194     public int getCompoundPaddingBottom() {
1195         final Drawables dr = mDrawables;
1196         if (dr == null || dr.mDrawableBottom == null) {
1197             return mPaddingBottom;
1198         } else {
1199             return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1200         }
1201     }
1202 
1203     /**
1204      * Returns the left padding of the view, plus space for the left
1205      * Drawable if any.
1206      */
getCompoundPaddingLeft()1207     public int getCompoundPaddingLeft() {
1208         final Drawables dr = mDrawables;
1209         if (dr == null || dr.mDrawableLeft == null) {
1210             return mPaddingLeft;
1211         } else {
1212             return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1213         }
1214     }
1215 
1216     /**
1217      * Returns the right padding of the view, plus space for the right
1218      * Drawable if any.
1219      */
getCompoundPaddingRight()1220     public int getCompoundPaddingRight() {
1221         final Drawables dr = mDrawables;
1222         if (dr == null || dr.mDrawableRight == null) {
1223             return mPaddingRight;
1224         } else {
1225             return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1226         }
1227     }
1228 
1229     /**
1230      * Returns the extended top padding of the view, including both the
1231      * top Drawable if any and any extra space to keep more than maxLines
1232      * of text from showing.  It is only valid to call this after measuring.
1233      */
getExtendedPaddingTop()1234     public int getExtendedPaddingTop() {
1235         if (mMaxMode != LINES) {
1236             return getCompoundPaddingTop();
1237         }
1238 
1239         if (mLayout.getLineCount() <= mMaximum) {
1240             return getCompoundPaddingTop();
1241         }
1242 
1243         int top = getCompoundPaddingTop();
1244         int bottom = getCompoundPaddingBottom();
1245         int viewht = getHeight() - top - bottom;
1246         int layoutht = mLayout.getLineTop(mMaximum);
1247 
1248         if (layoutht >= viewht) {
1249             return top;
1250         }
1251 
1252         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1253         if (gravity == Gravity.TOP) {
1254             return top;
1255         } else if (gravity == Gravity.BOTTOM) {
1256             return top + viewht - layoutht;
1257         } else { // (gravity == Gravity.CENTER_VERTICAL)
1258             return top + (viewht - layoutht) / 2;
1259         }
1260     }
1261 
1262     /**
1263      * Returns the extended bottom padding of the view, including both the
1264      * bottom Drawable if any and any extra space to keep more than maxLines
1265      * of text from showing.  It is only valid to call this after measuring.
1266      */
getExtendedPaddingBottom()1267     public int getExtendedPaddingBottom() {
1268         if (mMaxMode != LINES) {
1269             return getCompoundPaddingBottom();
1270         }
1271 
1272         if (mLayout.getLineCount() <= mMaximum) {
1273             return getCompoundPaddingBottom();
1274         }
1275 
1276         int top = getCompoundPaddingTop();
1277         int bottom = getCompoundPaddingBottom();
1278         int viewht = getHeight() - top - bottom;
1279         int layoutht = mLayout.getLineTop(mMaximum);
1280 
1281         if (layoutht >= viewht) {
1282             return bottom;
1283         }
1284 
1285         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1286         if (gravity == Gravity.TOP) {
1287             return bottom + viewht - layoutht;
1288         } else if (gravity == Gravity.BOTTOM) {
1289             return bottom;
1290         } else { // (gravity == Gravity.CENTER_VERTICAL)
1291             return bottom + (viewht - layoutht) / 2;
1292         }
1293     }
1294 
1295     /**
1296      * Returns the total left padding of the view, including the left
1297      * Drawable if any.
1298      */
getTotalPaddingLeft()1299     public int getTotalPaddingLeft() {
1300         return getCompoundPaddingLeft();
1301     }
1302 
1303     /**
1304      * Returns the total right padding of the view, including the right
1305      * Drawable if any.
1306      */
getTotalPaddingRight()1307     public int getTotalPaddingRight() {
1308         return getCompoundPaddingRight();
1309     }
1310 
1311     /**
1312      * Returns the total top padding of the view, including the top
1313      * Drawable if any, the extra space to keep more than maxLines
1314      * from showing, and the vertical offset for gravity, if any.
1315      */
getTotalPaddingTop()1316     public int getTotalPaddingTop() {
1317         return getExtendedPaddingTop() + getVerticalOffset(true);
1318     }
1319 
1320     /**
1321      * Returns the total bottom padding of the view, including the bottom
1322      * Drawable if any, the extra space to keep more than maxLines
1323      * from showing, and the vertical offset for gravity, if any.
1324      */
getTotalPaddingBottom()1325     public int getTotalPaddingBottom() {
1326         return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1327     }
1328 
1329     /**
1330      * Sets the Drawables (if any) to appear to the left of, above,
1331      * to the right of, and below the text.  Use null if you do not
1332      * want a Drawable there.  The Drawables must already have had
1333      * {@link Drawable#setBounds} called.
1334      *
1335      * @attr ref android.R.styleable#TextView_drawableLeft
1336      * @attr ref android.R.styleable#TextView_drawableTop
1337      * @attr ref android.R.styleable#TextView_drawableRight
1338      * @attr ref android.R.styleable#TextView_drawableBottom
1339      */
setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)1340     public void setCompoundDrawables(Drawable left, Drawable top,
1341                                      Drawable right, Drawable bottom) {
1342         Drawables dr = mDrawables;
1343 
1344         final boolean drawables = left != null || top != null
1345                 || right != null || bottom != null;
1346 
1347         if (!drawables) {
1348             // Clearing drawables...  can we free the data structure?
1349             if (dr != null) {
1350                 if (dr.mDrawablePadding == 0) {
1351                     mDrawables = null;
1352                 } else {
1353                     // We need to retain the last set padding, so just clear
1354                     // out all of the fields in the existing structure.
1355                     if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
1356                     dr.mDrawableLeft = null;
1357                     if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
1358                     dr.mDrawableTop = null;
1359                     if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
1360                     dr.mDrawableRight = null;
1361                     if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
1362                     dr.mDrawableBottom = null;
1363                     dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1364                     dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1365                     dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1366                     dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1367                 }
1368             }
1369         } else {
1370             if (dr == null) {
1371                 mDrawables = dr = new Drawables();
1372             }
1373 
1374             if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1375                 dr.mDrawableLeft.setCallback(null);
1376             }
1377             dr.mDrawableLeft = left;
1378             if (dr.mDrawableTop != left && dr.mDrawableTop != null) {
1379                 dr.mDrawableTop.setCallback(null);
1380             }
1381             dr.mDrawableTop = top;
1382             if (dr.mDrawableRight != left && dr.mDrawableRight != null) {
1383                 dr.mDrawableRight.setCallback(null);
1384             }
1385             dr.mDrawableRight = right;
1386             if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) {
1387                 dr.mDrawableBottom.setCallback(null);
1388             }
1389             dr.mDrawableBottom = bottom;
1390 
1391             final Rect compoundRect = dr.mCompoundRect;
1392             int[] state;
1393 
1394             state = getDrawableState();
1395 
1396             if (left != null) {
1397                 left.setState(state);
1398                 left.copyBounds(compoundRect);
1399                 left.setCallback(this);
1400                 dr.mDrawableSizeLeft = compoundRect.width();
1401                 dr.mDrawableHeightLeft = compoundRect.height();
1402             } else {
1403                 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1404             }
1405 
1406             if (right != null) {
1407                 right.setState(state);
1408                 right.copyBounds(compoundRect);
1409                 right.setCallback(this);
1410                 dr.mDrawableSizeRight = compoundRect.width();
1411                 dr.mDrawableHeightRight = compoundRect.height();
1412             } else {
1413                 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1414             }
1415 
1416             if (top != null) {
1417                 top.setState(state);
1418                 top.copyBounds(compoundRect);
1419                 top.setCallback(this);
1420                 dr.mDrawableSizeTop = compoundRect.height();
1421                 dr.mDrawableWidthTop = compoundRect.width();
1422             } else {
1423                 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1424             }
1425 
1426             if (bottom != null) {
1427                 bottom.setState(state);
1428                 bottom.copyBounds(compoundRect);
1429                 bottom.setCallback(this);
1430                 dr.mDrawableSizeBottom = compoundRect.height();
1431                 dr.mDrawableWidthBottom = compoundRect.width();
1432             } else {
1433                 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1434             }
1435         }
1436 
1437         invalidate();
1438         requestLayout();
1439     }
1440 
1441     /**
1442      * Sets the Drawables (if any) to appear to the left of, above,
1443      * to the right of, and below the text.  Use 0 if you do not
1444      * want a Drawable there. The Drawables' bounds will be set to
1445      * their intrinsic bounds.
1446      *
1447      * @param left Resource identifier of the left Drawable.
1448      * @param top Resource identifier of the top Drawable.
1449      * @param right Resource identifier of the right Drawable.
1450      * @param bottom Resource identifier of the bottom Drawable.
1451      *
1452      * @attr ref android.R.styleable#TextView_drawableLeft
1453      * @attr ref android.R.styleable#TextView_drawableTop
1454      * @attr ref android.R.styleable#TextView_drawableRight
1455      * @attr ref android.R.styleable#TextView_drawableBottom
1456      */
setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)1457     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
1458         final Resources resources = getContext().getResources();
1459         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
1460                 top != 0 ? resources.getDrawable(top) : null,
1461                 right != 0 ? resources.getDrawable(right) : null,
1462                 bottom != 0 ? resources.getDrawable(bottom) : null);
1463     }
1464 
1465     /**
1466      * Sets the Drawables (if any) to appear to the left of, above,
1467      * to the right of, and below the text.  Use null if you do not
1468      * want a Drawable there. The Drawables' bounds will be set to
1469      * their intrinsic bounds.
1470      *
1471      * @attr ref android.R.styleable#TextView_drawableLeft
1472      * @attr ref android.R.styleable#TextView_drawableTop
1473      * @attr ref android.R.styleable#TextView_drawableRight
1474      * @attr ref android.R.styleable#TextView_drawableBottom
1475      */
setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)1476     public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
1477             Drawable right, Drawable bottom) {
1478 
1479         if (left != null) {
1480             left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
1481         }
1482         if (right != null) {
1483             right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
1484         }
1485         if (top != null) {
1486             top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
1487         }
1488         if (bottom != null) {
1489             bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
1490         }
1491         setCompoundDrawables(left, top, right, bottom);
1492     }
1493 
1494     /**
1495      * Returns drawables for the left, top, right, and bottom borders.
1496      */
getCompoundDrawables()1497     public Drawable[] getCompoundDrawables() {
1498         final Drawables dr = mDrawables;
1499         if (dr != null) {
1500             return new Drawable[] {
1501                 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
1502             };
1503         } else {
1504             return new Drawable[] { null, null, null, null };
1505         }
1506     }
1507 
1508     /**
1509      * Sets the size of the padding between the compound drawables and
1510      * the text.
1511      *
1512      * @attr ref android.R.styleable#TextView_drawablePadding
1513      */
setCompoundDrawablePadding(int pad)1514     public void setCompoundDrawablePadding(int pad) {
1515         Drawables dr = mDrawables;
1516         if (pad == 0) {
1517             if (dr != null) {
1518                 dr.mDrawablePadding = pad;
1519             }
1520         } else {
1521             if (dr == null) {
1522                 mDrawables = dr = new Drawables();
1523             }
1524             dr.mDrawablePadding = pad;
1525         }
1526 
1527         invalidate();
1528         requestLayout();
1529     }
1530 
1531     /**
1532      * Returns the padding between the compound drawables and the text.
1533      */
getCompoundDrawablePadding()1534     public int getCompoundDrawablePadding() {
1535         final Drawables dr = mDrawables;
1536         return dr != null ? dr.mDrawablePadding : 0;
1537     }
1538 
1539     @Override
setPadding(int left, int top, int right, int bottom)1540     public void setPadding(int left, int top, int right, int bottom) {
1541         if (left != mPaddingLeft ||
1542             right != mPaddingRight ||
1543             top != mPaddingTop ||
1544             bottom != mPaddingBottom) {
1545             nullLayouts();
1546         }
1547 
1548         // the super call will requestLayout()
1549         super.setPadding(left, top, right, bottom);
1550         invalidate();
1551     }
1552 
1553     /**
1554      * Gets the autolink mask of the text.  See {@link
1555      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1556      * possible values.
1557      *
1558      * @attr ref android.R.styleable#TextView_autoLink
1559      */
getAutoLinkMask()1560     public final int getAutoLinkMask() {
1561         return mAutoLinkMask;
1562     }
1563 
1564     /**
1565      * Sets the text color, size, style, hint color, and highlight color
1566      * from the specified TextAppearance resource.
1567      */
setTextAppearance(Context context, int resid)1568     public void setTextAppearance(Context context, int resid) {
1569         TypedArray appearance =
1570             context.obtainStyledAttributes(resid,
1571                                            com.android.internal.R.styleable.TextAppearance);
1572 
1573         int color;
1574         ColorStateList colors;
1575         int ts;
1576 
1577         color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
1578         if (color != 0) {
1579             setHighlightColor(color);
1580         }
1581 
1582         colors = appearance.getColorStateList(com.android.internal.R.styleable.
1583                                               TextAppearance_textColor);
1584         if (colors != null) {
1585             setTextColor(colors);
1586         }
1587 
1588         ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
1589                                               TextAppearance_textSize, 0);
1590         if (ts != 0) {
1591             setRawTextSize(ts);
1592         }
1593 
1594         colors = appearance.getColorStateList(com.android.internal.R.styleable.
1595                                               TextAppearance_textColorHint);
1596         if (colors != null) {
1597             setHintTextColor(colors);
1598         }
1599 
1600         colors = appearance.getColorStateList(com.android.internal.R.styleable.
1601                                               TextAppearance_textColorLink);
1602         if (colors != null) {
1603             setLinkTextColor(colors);
1604         }
1605 
1606         int typefaceIndex, styleIndex;
1607 
1608         typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
1609                                           TextAppearance_typeface, -1);
1610         styleIndex = appearance.getInt(com.android.internal.R.styleable.
1611                                        TextAppearance_textStyle, -1);
1612 
1613         setTypefaceByIndex(typefaceIndex, styleIndex);
1614         appearance.recycle();
1615     }
1616 
1617     /**
1618      * @return the size (in pixels) of the default text size in this TextView.
1619      */
getTextSize()1620     public float getTextSize() {
1621         return mTextPaint.getTextSize();
1622     }
1623 
1624     /**
1625      * Set the default text size to the given value, interpreted as "scaled
1626      * pixel" units.  This size is adjusted based on the current density and
1627      * user font size preference.
1628      *
1629      * @param size The scaled pixel size.
1630      *
1631      * @attr ref android.R.styleable#TextView_textSize
1632      */
1633     @android.view.RemotableViewMethod
setTextSize(float size)1634     public void setTextSize(float size) {
1635         setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
1636     }
1637 
1638     /**
1639      * Set the default text size to a given unit and value.  See {@link
1640      * TypedValue} for the possible dimension units.
1641      *
1642      * @param unit The desired dimension unit.
1643      * @param size The desired size in the given units.
1644      *
1645      * @attr ref android.R.styleable#TextView_textSize
1646      */
setTextSize(int unit, float size)1647     public void setTextSize(int unit, float size) {
1648         Context c = getContext();
1649         Resources r;
1650 
1651         if (c == null)
1652             r = Resources.getSystem();
1653         else
1654             r = c.getResources();
1655 
1656         setRawTextSize(TypedValue.applyDimension(
1657             unit, size, r.getDisplayMetrics()));
1658     }
1659 
setRawTextSize(float size)1660     private void setRawTextSize(float size) {
1661         if (size != mTextPaint.getTextSize()) {
1662             mTextPaint.setTextSize(size);
1663 
1664             if (mLayout != null) {
1665                 nullLayouts();
1666                 requestLayout();
1667                 invalidate();
1668             }
1669         }
1670     }
1671 
1672     /**
1673      * @return the extent by which text is currently being stretched
1674      * horizontally.  This will usually be 1.
1675      */
getTextScaleX()1676     public float getTextScaleX() {
1677         return mTextPaint.getTextScaleX();
1678     }
1679 
1680     /**
1681      * Sets the extent by which text should be stretched horizontally.
1682      *
1683      * @attr ref android.R.styleable#TextView_textScaleX
1684      */
1685     @android.view.RemotableViewMethod
setTextScaleX(float size)1686     public void setTextScaleX(float size) {
1687         if (size != mTextPaint.getTextScaleX()) {
1688             mUserSetTextScaleX = true;
1689             mTextPaint.setTextScaleX(size);
1690 
1691             if (mLayout != null) {
1692                 nullLayouts();
1693                 requestLayout();
1694                 invalidate();
1695             }
1696         }
1697     }
1698 
1699     /**
1700      * Sets the typeface and style in which the text should be displayed.
1701      * Note that not all Typeface families actually have bold and italic
1702      * variants, so you may need to use
1703      * {@link #setTypeface(Typeface, int)} to get the appearance
1704      * that you actually want.
1705      *
1706      * @attr ref android.R.styleable#TextView_typeface
1707      * @attr ref android.R.styleable#TextView_textStyle
1708      */
setTypeface(Typeface tf)1709     public void setTypeface(Typeface tf) {
1710         if (mTextPaint.getTypeface() != tf) {
1711             mTextPaint.setTypeface(tf);
1712 
1713             if (mLayout != null) {
1714                 nullLayouts();
1715                 requestLayout();
1716                 invalidate();
1717             }
1718         }
1719     }
1720 
1721     /**
1722      * @return the current typeface and style in which the text is being
1723      * displayed.
1724      */
getTypeface()1725     public Typeface getTypeface() {
1726         return mTextPaint.getTypeface();
1727     }
1728 
1729     /**
1730      * Sets the text color for all the states (normal, selected,
1731      * focused) to be this color.
1732      *
1733      * @attr ref android.R.styleable#TextView_textColor
1734      */
1735     @android.view.RemotableViewMethod
setTextColor(int color)1736     public void setTextColor(int color) {
1737         mTextColor = ColorStateList.valueOf(color);
1738         updateTextColors();
1739     }
1740 
1741     /**
1742      * Sets the text color.
1743      *
1744      * @attr ref android.R.styleable#TextView_textColor
1745      */
setTextColor(ColorStateList colors)1746     public void setTextColor(ColorStateList colors) {
1747         if (colors == null) {
1748             throw new NullPointerException();
1749         }
1750 
1751         mTextColor = colors;
1752         updateTextColors();
1753     }
1754 
1755     /**
1756      * Return the set of text colors.
1757      *
1758      * @return Returns the set of text colors.
1759      */
getTextColors()1760     public final ColorStateList getTextColors() {
1761         return mTextColor;
1762     }
1763 
1764     /**
1765      * <p>Return the current color selected for normal text.</p>
1766      *
1767      * @return Returns the current text color.
1768      */
getCurrentTextColor()1769     public final int getCurrentTextColor() {
1770         return mCurTextColor;
1771     }
1772 
1773     /**
1774      * Sets the color used to display the selection highlight.
1775      *
1776      * @attr ref android.R.styleable#TextView_textColorHighlight
1777      */
1778     @android.view.RemotableViewMethod
setHighlightColor(int color)1779     public void setHighlightColor(int color) {
1780         if (mHighlightColor != color) {
1781             mHighlightColor = color;
1782             invalidate();
1783         }
1784     }
1785 
1786     /**
1787      * Gives the text a shadow of the specified radius and color, the specified
1788      * distance from its normal position.
1789      *
1790      * @attr ref android.R.styleable#TextView_shadowColor
1791      * @attr ref android.R.styleable#TextView_shadowDx
1792      * @attr ref android.R.styleable#TextView_shadowDy
1793      * @attr ref android.R.styleable#TextView_shadowRadius
1794      */
setShadowLayer(float radius, float dx, float dy, int color)1795     public void setShadowLayer(float radius, float dx, float dy, int color) {
1796         mTextPaint.setShadowLayer(radius, dx, dy, color);
1797 
1798         mShadowRadius = radius;
1799         mShadowDx = dx;
1800         mShadowDy = dy;
1801 
1802         invalidate();
1803     }
1804 
1805     /**
1806      * @return the base paint used for the text.  Please use this only to
1807      * consult the Paint's properties and not to change them.
1808      */
getPaint()1809     public TextPaint getPaint() {
1810         return mTextPaint;
1811     }
1812 
1813     /**
1814      * Sets the autolink mask of the text.  See {@link
1815      * android.text.util.Linkify#ALL Linkify.ALL} and peers for
1816      * possible values.
1817      *
1818      * @attr ref android.R.styleable#TextView_autoLink
1819      */
1820     @android.view.RemotableViewMethod
setAutoLinkMask(int mask)1821     public final void setAutoLinkMask(int mask) {
1822         mAutoLinkMask = mask;
1823     }
1824 
1825     /**
1826      * Sets whether the movement method will automatically be set to
1827      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1828      * set to nonzero and links are detected in {@link #setText}.
1829      * The default is true.
1830      *
1831      * @attr ref android.R.styleable#TextView_linksClickable
1832      */
1833     @android.view.RemotableViewMethod
setLinksClickable(boolean whether)1834     public final void setLinksClickable(boolean whether) {
1835         mLinksClickable = whether;
1836     }
1837 
1838     /**
1839      * Returns whether the movement method will automatically be set to
1840      * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
1841      * set to nonzero and links are detected in {@link #setText}.
1842      * The default is true.
1843      *
1844      * @attr ref android.R.styleable#TextView_linksClickable
1845      */
getLinksClickable()1846     public final boolean getLinksClickable() {
1847         return mLinksClickable;
1848     }
1849 
1850     /**
1851      * Returns the list of URLSpans attached to the text
1852      * (by {@link Linkify} or otherwise) if any.  You can call
1853      * {@link URLSpan#getURL} on them to find where they link to
1854      * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
1855      * to find the region of the text they are attached to.
1856      */
getUrls()1857     public URLSpan[] getUrls() {
1858         if (mText instanceof Spanned) {
1859             return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
1860         } else {
1861             return new URLSpan[0];
1862         }
1863     }
1864 
1865     /**
1866      * Sets the color of the hint text.
1867      *
1868      * @attr ref android.R.styleable#TextView_textColorHint
1869      */
1870     @android.view.RemotableViewMethod
setHintTextColor(int color)1871     public final void setHintTextColor(int color) {
1872         mHintTextColor = ColorStateList.valueOf(color);
1873         updateTextColors();
1874     }
1875 
1876     /**
1877      * Sets the color of the hint text.
1878      *
1879      * @attr ref android.R.styleable#TextView_textColorHint
1880      */
setHintTextColor(ColorStateList colors)1881     public final void setHintTextColor(ColorStateList colors) {
1882         mHintTextColor = colors;
1883         updateTextColors();
1884     }
1885 
1886     /**
1887      * <p>Return the color used to paint the hint text.</p>
1888      *
1889      * @return Returns the list of hint text colors.
1890      */
getHintTextColors()1891     public final ColorStateList getHintTextColors() {
1892         return mHintTextColor;
1893     }
1894 
1895     /**
1896      * <p>Return the current color selected to paint the hint text.</p>
1897      *
1898      * @return Returns the current hint text color.
1899      */
getCurrentHintTextColor()1900     public final int getCurrentHintTextColor() {
1901         return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
1902     }
1903 
1904     /**
1905      * Sets the color of links in the text.
1906      *
1907      * @attr ref android.R.styleable#TextView_textColorLink
1908      */
1909     @android.view.RemotableViewMethod
setLinkTextColor(int color)1910     public final void setLinkTextColor(int color) {
1911         mLinkTextColor = ColorStateList.valueOf(color);
1912         updateTextColors();
1913     }
1914 
1915     /**
1916      * Sets the color of links in the text.
1917      *
1918      * @attr ref android.R.styleable#TextView_textColorLink
1919      */
setLinkTextColor(ColorStateList colors)1920     public final void setLinkTextColor(ColorStateList colors) {
1921         mLinkTextColor = colors;
1922         updateTextColors();
1923     }
1924 
1925     /**
1926      * <p>Returns the color used to paint links in the text.</p>
1927      *
1928      * @return Returns the list of link text colors.
1929      */
getLinkTextColors()1930     public final ColorStateList getLinkTextColors() {
1931         return mLinkTextColor;
1932     }
1933 
1934     /**
1935      * Sets the horizontal alignment of the text and the
1936      * vertical gravity that will be used when there is extra space
1937      * in the TextView beyond what is required for the text itself.
1938      *
1939      * @see android.view.Gravity
1940      * @attr ref android.R.styleable#TextView_gravity
1941      */
setGravity(int gravity)1942     public void setGravity(int gravity) {
1943         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
1944             gravity |= Gravity.LEFT;
1945         }
1946         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
1947             gravity |= Gravity.TOP;
1948         }
1949 
1950         boolean newLayout = false;
1951 
1952         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) !=
1953             (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) {
1954             newLayout = true;
1955         }
1956 
1957         if (gravity != mGravity) {
1958             invalidate();
1959         }
1960 
1961         mGravity = gravity;
1962 
1963         if (mLayout != null && newLayout) {
1964             // XXX this is heavy-handed because no actual content changes.
1965             int want = mLayout.getWidth();
1966             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
1967 
1968             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
1969                           mRight - mLeft - getCompoundPaddingLeft() -
1970                           getCompoundPaddingRight(), true);
1971         }
1972     }
1973 
1974     /**
1975      * Returns the horizontal and vertical alignment of this TextView.
1976      *
1977      * @see android.view.Gravity
1978      * @attr ref android.R.styleable#TextView_gravity
1979      */
getGravity()1980     public int getGravity() {
1981         return mGravity;
1982     }
1983 
1984     /**
1985      * @return the flags on the Paint being used to display the text.
1986      * @see Paint#getFlags
1987      */
getPaintFlags()1988     public int getPaintFlags() {
1989         return mTextPaint.getFlags();
1990     }
1991 
1992     /**
1993      * Sets flags on the Paint being used to display the text and
1994      * reflows the text if they are different from the old flags.
1995      * @see Paint#setFlags
1996      */
1997     @android.view.RemotableViewMethod
setPaintFlags(int flags)1998     public void setPaintFlags(int flags) {
1999         if (mTextPaint.getFlags() != flags) {
2000             mTextPaint.setFlags(flags);
2001 
2002             if (mLayout != null) {
2003                 nullLayouts();
2004                 requestLayout();
2005                 invalidate();
2006             }
2007         }
2008     }
2009 
2010     /**
2011      * Sets whether the text should be allowed to be wider than the
2012      * View is.  If false, it will be wrapped to the width of the View.
2013      *
2014      * @attr ref android.R.styleable#TextView_scrollHorizontally
2015      */
setHorizontallyScrolling(boolean whether)2016     public void setHorizontallyScrolling(boolean whether) {
2017         mHorizontallyScrolling = whether;
2018 
2019         if (mLayout != null) {
2020             nullLayouts();
2021             requestLayout();
2022             invalidate();
2023         }
2024     }
2025 
2026     /**
2027      * Makes the TextView at least this many lines tall
2028      *
2029      * @attr ref android.R.styleable#TextView_minLines
2030      */
2031     @android.view.RemotableViewMethod
setMinLines(int minlines)2032     public void setMinLines(int minlines) {
2033         mMinimum = minlines;
2034         mMinMode = LINES;
2035 
2036         requestLayout();
2037         invalidate();
2038     }
2039 
2040     /**
2041      * Makes the TextView at least this many pixels tall
2042      *
2043      * @attr ref android.R.styleable#TextView_minHeight
2044      */
2045     @android.view.RemotableViewMethod
setMinHeight(int minHeight)2046     public void setMinHeight(int minHeight) {
2047         mMinimum = minHeight;
2048         mMinMode = PIXELS;
2049 
2050         requestLayout();
2051         invalidate();
2052     }
2053 
2054     /**
2055      * Makes the TextView at most this many lines tall
2056      *
2057      * @attr ref android.R.styleable#TextView_maxLines
2058      */
2059     @android.view.RemotableViewMethod
setMaxLines(int maxlines)2060     public void setMaxLines(int maxlines) {
2061         mMaximum = maxlines;
2062         mMaxMode = LINES;
2063 
2064         requestLayout();
2065         invalidate();
2066     }
2067 
2068     /**
2069      * Makes the TextView at most this many pixels tall
2070      *
2071      * @attr ref android.R.styleable#TextView_maxHeight
2072      */
2073     @android.view.RemotableViewMethod
setMaxHeight(int maxHeight)2074     public void setMaxHeight(int maxHeight) {
2075         mMaximum = maxHeight;
2076         mMaxMode = PIXELS;
2077 
2078         requestLayout();
2079         invalidate();
2080     }
2081 
2082     /**
2083      * Makes the TextView exactly this many lines tall
2084      *
2085      * @attr ref android.R.styleable#TextView_lines
2086      */
2087     @android.view.RemotableViewMethod
setLines(int lines)2088     public void setLines(int lines) {
2089         mMaximum = mMinimum = lines;
2090         mMaxMode = mMinMode = LINES;
2091 
2092         requestLayout();
2093         invalidate();
2094     }
2095 
2096     /**
2097      * Makes the TextView exactly this many pixels tall.
2098      * You could do the same thing by specifying this number in the
2099      * LayoutParams.
2100      *
2101      * @attr ref android.R.styleable#TextView_height
2102      */
2103     @android.view.RemotableViewMethod
setHeight(int pixels)2104     public void setHeight(int pixels) {
2105         mMaximum = mMinimum = pixels;
2106         mMaxMode = mMinMode = PIXELS;
2107 
2108         requestLayout();
2109         invalidate();
2110     }
2111 
2112     /**
2113      * Makes the TextView at least this many ems wide
2114      *
2115      * @attr ref android.R.styleable#TextView_minEms
2116      */
2117     @android.view.RemotableViewMethod
setMinEms(int minems)2118     public void setMinEms(int minems) {
2119         mMinWidth = minems;
2120         mMinWidthMode = EMS;
2121 
2122         requestLayout();
2123         invalidate();
2124     }
2125 
2126     /**
2127      * Makes the TextView at least this many pixels wide
2128      *
2129      * @attr ref android.R.styleable#TextView_minWidth
2130      */
2131     @android.view.RemotableViewMethod
setMinWidth(int minpixels)2132     public void setMinWidth(int minpixels) {
2133         mMinWidth = minpixels;
2134         mMinWidthMode = PIXELS;
2135 
2136         requestLayout();
2137         invalidate();
2138     }
2139 
2140     /**
2141      * Makes the TextView at most this many ems wide
2142      *
2143      * @attr ref android.R.styleable#TextView_maxEms
2144      */
2145     @android.view.RemotableViewMethod
setMaxEms(int maxems)2146     public void setMaxEms(int maxems) {
2147         mMaxWidth = maxems;
2148         mMaxWidthMode = EMS;
2149 
2150         requestLayout();
2151         invalidate();
2152     }
2153 
2154     /**
2155      * Makes the TextView at most this many pixels wide
2156      *
2157      * @attr ref android.R.styleable#TextView_maxWidth
2158      */
2159     @android.view.RemotableViewMethod
setMaxWidth(int maxpixels)2160     public void setMaxWidth(int maxpixels) {
2161         mMaxWidth = maxpixels;
2162         mMaxWidthMode = PIXELS;
2163 
2164         requestLayout();
2165         invalidate();
2166     }
2167 
2168     /**
2169      * Makes the TextView exactly this many ems wide
2170      *
2171      * @attr ref android.R.styleable#TextView_ems
2172      */
2173     @android.view.RemotableViewMethod
setEms(int ems)2174     public void setEms(int ems) {
2175         mMaxWidth = mMinWidth = ems;
2176         mMaxWidthMode = mMinWidthMode = EMS;
2177 
2178         requestLayout();
2179         invalidate();
2180     }
2181 
2182     /**
2183      * Makes the TextView exactly this many pixels wide.
2184      * You could do the same thing by specifying this number in the
2185      * LayoutParams.
2186      *
2187      * @attr ref android.R.styleable#TextView_width
2188      */
2189     @android.view.RemotableViewMethod
setWidth(int pixels)2190     public void setWidth(int pixels) {
2191         mMaxWidth = mMinWidth = pixels;
2192         mMaxWidthMode = mMinWidthMode = PIXELS;
2193 
2194         requestLayout();
2195         invalidate();
2196     }
2197 
2198 
2199     /**
2200      * Sets line spacing for this TextView.  Each line will have its height
2201      * multiplied by <code>mult</code> and have <code>add</code> added to it.
2202      *
2203      * @attr ref android.R.styleable#TextView_lineSpacingExtra
2204      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
2205      */
setLineSpacing(float add, float mult)2206     public void setLineSpacing(float add, float mult) {
2207         mSpacingMult = mult;
2208         mSpacingAdd = add;
2209 
2210         if (mLayout != null) {
2211             nullLayouts();
2212             requestLayout();
2213             invalidate();
2214         }
2215     }
2216 
2217     /**
2218      * Convenience method: Append the specified text to the TextView's
2219      * display buffer, upgrading it to BufferType.EDITABLE if it was
2220      * not already editable.
2221      */
append(CharSequence text)2222     public final void append(CharSequence text) {
2223         append(text, 0, text.length());
2224     }
2225 
2226     /**
2227      * Convenience method: Append the specified text slice to the TextView's
2228      * display buffer, upgrading it to BufferType.EDITABLE if it was
2229      * not already editable.
2230      */
append(CharSequence text, int start, int end)2231     public void append(CharSequence text, int start, int end) {
2232         if (!(mText instanceof Editable)) {
2233             setText(mText, BufferType.EDITABLE);
2234         }
2235 
2236         ((Editable) mText).append(text, start, end);
2237     }
2238 
updateTextColors()2239     private void updateTextColors() {
2240         boolean inval = false;
2241         int color = mTextColor.getColorForState(getDrawableState(), 0);
2242         if (color != mCurTextColor) {
2243             mCurTextColor = color;
2244             inval = true;
2245         }
2246         if (mLinkTextColor != null) {
2247             color = mLinkTextColor.getColorForState(getDrawableState(), 0);
2248             if (color != mTextPaint.linkColor) {
2249                 mTextPaint.linkColor = color;
2250                 inval = true;
2251             }
2252         }
2253         if (mHintTextColor != null) {
2254             color = mHintTextColor.getColorForState(getDrawableState(), 0);
2255             if (color != mCurHintTextColor && mText.length() == 0) {
2256                 mCurHintTextColor = color;
2257                 inval = true;
2258             }
2259         }
2260         if (inval) {
2261             invalidate();
2262         }
2263     }
2264 
2265     @Override
drawableStateChanged()2266     protected void drawableStateChanged() {
2267         super.drawableStateChanged();
2268         if (mTextColor != null && mTextColor.isStateful()
2269                 || (mHintTextColor != null && mHintTextColor.isStateful())
2270                 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
2271             updateTextColors();
2272         }
2273 
2274         final Drawables dr = mDrawables;
2275         if (dr != null) {
2276             int[] state = getDrawableState();
2277             if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
2278                 dr.mDrawableTop.setState(state);
2279             }
2280             if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
2281                 dr.mDrawableBottom.setState(state);
2282             }
2283             if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
2284                 dr.mDrawableLeft.setState(state);
2285             }
2286             if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
2287                 dr.mDrawableRight.setState(state);
2288             }
2289         }
2290     }
2291 
2292     /**
2293      * User interface state that is stored by TextView for implementing
2294      * {@link View#onSaveInstanceState}.
2295      */
2296     public static class SavedState extends BaseSavedState {
2297         int selStart;
2298         int selEnd;
2299         CharSequence text;
2300         boolean frozenWithFocus;
2301         CharSequence error;
2302 
SavedState(Parcelable superState)2303         SavedState(Parcelable superState) {
2304             super(superState);
2305         }
2306 
2307         @Override
writeToParcel(Parcel out, int flags)2308         public void writeToParcel(Parcel out, int flags) {
2309             super.writeToParcel(out, flags);
2310             out.writeInt(selStart);
2311             out.writeInt(selEnd);
2312             out.writeInt(frozenWithFocus ? 1 : 0);
2313             TextUtils.writeToParcel(text, out, flags);
2314 
2315             if (error == null) {
2316                 out.writeInt(0);
2317             } else {
2318                 out.writeInt(1);
2319                 TextUtils.writeToParcel(error, out, flags);
2320             }
2321         }
2322 
2323         @Override
toString()2324         public String toString() {
2325             String str = "TextView.SavedState{"
2326                     + Integer.toHexString(System.identityHashCode(this))
2327                     + " start=" + selStart + " end=" + selEnd;
2328             if (text != null) {
2329                 str += " text=" + text;
2330             }
2331             return str + "}";
2332         }
2333 
2334         public static final Parcelable.Creator<SavedState> CREATOR
2335                 = new Parcelable.Creator<SavedState>() {
2336             public SavedState createFromParcel(Parcel in) {
2337                 return new SavedState(in);
2338             }
2339 
2340             public SavedState[] newArray(int size) {
2341                 return new SavedState[size];
2342             }
2343         };
2344 
SavedState(Parcel in)2345         private SavedState(Parcel in) {
2346             super(in);
2347             selStart = in.readInt();
2348             selEnd = in.readInt();
2349             frozenWithFocus = (in.readInt() != 0);
2350             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2351 
2352             if (in.readInt() != 0) {
2353                 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
2354             }
2355         }
2356     }
2357 
2358     @Override
onSaveInstanceState()2359     public Parcelable onSaveInstanceState() {
2360         Parcelable superState = super.onSaveInstanceState();
2361 
2362         // Save state if we are forced to
2363         boolean save = mFreezesText;
2364         int start = 0;
2365         int end = 0;
2366 
2367         if (mText != null) {
2368             start = Selection.getSelectionStart(mText);
2369             end = Selection.getSelectionEnd(mText);
2370             if (start >= 0 || end >= 0) {
2371                 // Or save state if there is a selection
2372                 save = true;
2373             }
2374         }
2375 
2376         if (save) {
2377             SavedState ss = new SavedState(superState);
2378             // XXX Should also save the current scroll position!
2379             ss.selStart = start;
2380             ss.selEnd = end;
2381 
2382             if (mText instanceof Spanned) {
2383                 /*
2384                  * Calling setText() strips off any ChangeWatchers;
2385                  * strip them now to avoid leaking references.
2386                  * But do it to a copy so that if there are any
2387                  * further changes to the text of this view, it
2388                  * won't get into an inconsistent state.
2389                  */
2390 
2391                 Spannable sp = new SpannableString(mText);
2392 
2393                 for (ChangeWatcher cw :
2394                      sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2395                     sp.removeSpan(cw);
2396                 }
2397 
2398                 ss.text = sp;
2399             } else {
2400                 ss.text = mText.toString();
2401             }
2402 
2403             if (isFocused() && start >= 0 && end >= 0) {
2404                 ss.frozenWithFocus = true;
2405             }
2406 
2407             ss.error = mError;
2408 
2409             return ss;
2410         }
2411 
2412         return superState;
2413     }
2414 
2415     @Override
onRestoreInstanceState(Parcelable state)2416     public void onRestoreInstanceState(Parcelable state) {
2417         if (!(state instanceof SavedState)) {
2418             super.onRestoreInstanceState(state);
2419             return;
2420         }
2421 
2422         SavedState ss = (SavedState)state;
2423         super.onRestoreInstanceState(ss.getSuperState());
2424 
2425         // XXX restore buffer type too, as well as lots of other stuff
2426         if (ss.text != null) {
2427             setText(ss.text);
2428         }
2429 
2430         if (ss.selStart >= 0 && ss.selEnd >= 0) {
2431             if (mText instanceof Spannable) {
2432                 int len = mText.length();
2433 
2434                 if (ss.selStart > len || ss.selEnd > len) {
2435                     String restored = "";
2436 
2437                     if (ss.text != null) {
2438                         restored = "(restored) ";
2439                     }
2440 
2441                     Log.e("TextView", "Saved cursor position " + ss.selStart +
2442                           "/" + ss.selEnd + " out of range for " + restored +
2443                           "text " + mText);
2444                 } else {
2445                     Selection.setSelection((Spannable) mText, ss.selStart,
2446                                            ss.selEnd);
2447 
2448                     if (ss.frozenWithFocus) {
2449                         mFrozenWithFocus = true;
2450                     }
2451                 }
2452             }
2453         }
2454 
2455         if (ss.error != null) {
2456             final CharSequence error = ss.error;
2457             // Display the error later, after the first layout pass
2458             post(new Runnable() {
2459                 public void run() {
2460                     setError(error);
2461                 }
2462             });
2463         }
2464     }
2465 
2466     /**
2467      * Control whether this text view saves its entire text contents when
2468      * freezing to an icicle, in addition to dynamic state such as cursor
2469      * position.  By default this is false, not saving the text.  Set to true
2470      * if the text in the text view is not being saved somewhere else in
2471      * persistent storage (such as in a content provider) so that if the
2472      * view is later thawed the user will not lose their data.
2473      *
2474      * @param freezesText Controls whether a frozen icicle should include the
2475      * entire text data: true to include it, false to not.
2476      *
2477      * @attr ref android.R.styleable#TextView_freezesText
2478      */
2479     @android.view.RemotableViewMethod
setFreezesText(boolean freezesText)2480     public void setFreezesText(boolean freezesText) {
2481         mFreezesText = freezesText;
2482     }
2483 
2484     /**
2485      * Return whether this text view is including its entire text contents
2486      * in frozen icicles.
2487      *
2488      * @return Returns true if text is included, false if it isn't.
2489      *
2490      * @see #setFreezesText
2491      */
getFreezesText()2492     public boolean getFreezesText() {
2493         return mFreezesText;
2494     }
2495 
2496     ///////////////////////////////////////////////////////////////////////////
2497 
2498     /**
2499      * Sets the Factory used to create new Editables.
2500      */
setEditableFactory(Editable.Factory factory)2501     public final void setEditableFactory(Editable.Factory factory) {
2502         mEditableFactory = factory;
2503         setText(mText);
2504     }
2505 
2506     /**
2507      * Sets the Factory used to create new Spannables.
2508      */
setSpannableFactory(Spannable.Factory factory)2509     public final void setSpannableFactory(Spannable.Factory factory) {
2510         mSpannableFactory = factory;
2511         setText(mText);
2512     }
2513 
2514     /**
2515      * Sets the string value of the TextView. TextView <em>does not</em> accept
2516      * HTML-like formatting, which you can do with text strings in XML resource files.
2517      * To style your strings, attach android.text.style.* objects to a
2518      * {@link android.text.SpannableString SpannableString}, or see the
2519      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
2520      * Available Resource Types</a> documentation for an example of setting
2521      * formatted text in the XML resource file.
2522      *
2523      * @attr ref android.R.styleable#TextView_text
2524      */
2525     @android.view.RemotableViewMethod
setText(CharSequence text)2526     public final void setText(CharSequence text) {
2527         setText(text, mBufferType);
2528     }
2529 
2530     /**
2531      * Like {@link #setText(CharSequence)},
2532      * except that the cursor position (if any) is retained in the new text.
2533      *
2534      * @param text The new text to place in the text view.
2535      *
2536      * @see #setText(CharSequence)
2537      */
2538     @android.view.RemotableViewMethod
setTextKeepState(CharSequence text)2539     public final void setTextKeepState(CharSequence text) {
2540         setTextKeepState(text, mBufferType);
2541     }
2542 
2543     /**
2544      * Sets the text that this TextView is to display (see
2545      * {@link #setText(CharSequence)}) and also sets whether it is stored
2546      * in a styleable/spannable buffer and whether it is editable.
2547      *
2548      * @attr ref android.R.styleable#TextView_text
2549      * @attr ref android.R.styleable#TextView_bufferType
2550      */
setText(CharSequence text, BufferType type)2551     public void setText(CharSequence text, BufferType type) {
2552         setText(text, type, true, 0);
2553 
2554         if (mCharWrapper != null) {
2555             mCharWrapper.mChars = null;
2556         }
2557     }
2558 
setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)2559     private void setText(CharSequence text, BufferType type,
2560                          boolean notifyBefore, int oldlen) {
2561         if (text == null) {
2562             text = "";
2563         }
2564 
2565         if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
2566 
2567         if (text instanceof Spanned &&
2568             ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
2569             setHorizontalFadingEdgeEnabled(true);
2570             setEllipsize(TextUtils.TruncateAt.MARQUEE);
2571         }
2572 
2573         int n = mFilters.length;
2574         for (int i = 0; i < n; i++) {
2575             CharSequence out = mFilters[i].filter(text, 0, text.length(),
2576                                                   EMPTY_SPANNED, 0, 0);
2577             if (out != null) {
2578                 text = out;
2579             }
2580         }
2581 
2582         if (notifyBefore) {
2583             if (mText != null) {
2584                 oldlen = mText.length();
2585                 sendBeforeTextChanged(mText, 0, oldlen, text.length());
2586             } else {
2587                 sendBeforeTextChanged("", 0, 0, text.length());
2588             }
2589         }
2590 
2591         boolean needEditableForNotification = false;
2592 
2593         if (mListeners != null && mListeners.size() != 0) {
2594             needEditableForNotification = true;
2595         }
2596 
2597         if (type == BufferType.EDITABLE || mInput != null ||
2598             needEditableForNotification) {
2599             Editable t = mEditableFactory.newEditable(text);
2600             text = t;
2601             setFilters(t, mFilters);
2602             InputMethodManager imm = InputMethodManager.peekInstance();
2603             if (imm != null) imm.restartInput(this);
2604         } else if (type == BufferType.SPANNABLE || mMovement != null) {
2605             text = mSpannableFactory.newSpannable(text);
2606         } else if (!(text instanceof CharWrapper)) {
2607             text = TextUtils.stringOrSpannedString(text);
2608         }
2609 
2610         if (mAutoLinkMask != 0) {
2611             Spannable s2;
2612 
2613             if (type == BufferType.EDITABLE || text instanceof Spannable) {
2614                 s2 = (Spannable) text;
2615             } else {
2616                 s2 = mSpannableFactory.newSpannable(text);
2617             }
2618 
2619             if (Linkify.addLinks(s2, mAutoLinkMask)) {
2620                 text = s2;
2621                 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
2622 
2623                 /*
2624                  * We must go ahead and set the text before changing the
2625                  * movement method, because setMovementMethod() may call
2626                  * setText() again to try to upgrade the buffer type.
2627                  */
2628                 mText = text;
2629 
2630                 if (mLinksClickable) {
2631                     setMovementMethod(LinkMovementMethod.getInstance());
2632                 }
2633             }
2634         }
2635 
2636         mBufferType = type;
2637         mText = text;
2638 
2639         if (mTransformation == null)
2640             mTransformed = text;
2641         else
2642             mTransformed = mTransformation.getTransformation(text, this);
2643 
2644         final int textLength = text.length();
2645 
2646         if (text instanceof Spannable) {
2647             Spannable sp = (Spannable) text;
2648 
2649             // Remove any ChangeWatchers that might have come
2650             // from other TextViews.
2651             final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
2652             final int count = watchers.length;
2653             for (int i = 0; i < count; i++)
2654                 sp.removeSpan(watchers[i]);
2655 
2656             if (mChangeWatcher == null)
2657                 mChangeWatcher = new ChangeWatcher();
2658 
2659             sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
2660                        (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
2661 
2662             if (mInput != null) {
2663                 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2664             }
2665 
2666             if (mTransformation != null) {
2667                 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2668 
2669             }
2670 
2671             if (mMovement != null) {
2672                 mMovement.initialize(this, (Spannable) text);
2673 
2674                 /*
2675                  * Initializing the movement method will have set the
2676                  * selection, so reset mSelectionMoved to keep that from
2677                  * interfering with the normal on-focus selection-setting.
2678                  */
2679                 mSelectionMoved = false;
2680             }
2681         }
2682 
2683         if (mLayout != null) {
2684             checkForRelayout();
2685         }
2686 
2687         sendOnTextChanged(text, 0, oldlen, textLength);
2688         onTextChanged(text, 0, oldlen, textLength);
2689 
2690         if (needEditableForNotification) {
2691             sendAfterTextChanged((Editable) text);
2692         }
2693     }
2694 
2695     /**
2696      * Sets the TextView to display the specified slice of the specified
2697      * char array.  You must promise that you will not change the contents
2698      * of the array except for right before another call to setText(),
2699      * since the TextView has no way to know that the text
2700      * has changed and that it needs to invalidate and re-layout.
2701      */
setText(char[] text, int start, int len)2702     public final void setText(char[] text, int start, int len) {
2703         int oldlen = 0;
2704 
2705         if (start < 0 || len < 0 || start + len > text.length) {
2706             throw new IndexOutOfBoundsException(start + ", " + len);
2707         }
2708 
2709         /*
2710          * We must do the before-notification here ourselves because if
2711          * the old text is a CharWrapper we destroy it before calling
2712          * into the normal path.
2713          */
2714         if (mText != null) {
2715             oldlen = mText.length();
2716             sendBeforeTextChanged(mText, 0, oldlen, len);
2717         } else {
2718             sendBeforeTextChanged("", 0, 0, len);
2719         }
2720 
2721         if (mCharWrapper == null) {
2722             mCharWrapper = new CharWrapper(text, start, len);
2723         } else {
2724             mCharWrapper.set(text, start, len);
2725         }
2726 
2727         setText(mCharWrapper, mBufferType, false, oldlen);
2728     }
2729 
2730     private static class CharWrapper
2731             implements CharSequence, GetChars, GraphicsOperations {
2732         private char[] mChars;
2733         private int mStart, mLength;
2734 
CharWrapper(char[] chars, int start, int len)2735         public CharWrapper(char[] chars, int start, int len) {
2736             mChars = chars;
2737             mStart = start;
2738             mLength = len;
2739         }
2740 
set(char[] chars, int start, int len)2741         /* package */ void set(char[] chars, int start, int len) {
2742             mChars = chars;
2743             mStart = start;
2744             mLength = len;
2745         }
2746 
length()2747         public int length() {
2748             return mLength;
2749         }
2750 
charAt(int off)2751         public char charAt(int off) {
2752             return mChars[off + mStart];
2753         }
2754 
toString()2755         public String toString() {
2756             return new String(mChars, mStart, mLength);
2757         }
2758 
subSequence(int start, int end)2759         public CharSequence subSequence(int start, int end) {
2760             if (start < 0 || end < 0 || start > mLength || end > mLength) {
2761                 throw new IndexOutOfBoundsException(start + ", " + end);
2762             }
2763 
2764             return new String(mChars, start + mStart, end - start);
2765         }
2766 
getChars(int start, int end, char[] buf, int off)2767         public void getChars(int start, int end, char[] buf, int off) {
2768             if (start < 0 || end < 0 || start > mLength || end > mLength) {
2769                 throw new IndexOutOfBoundsException(start + ", " + end);
2770             }
2771 
2772             System.arraycopy(mChars, start + mStart, buf, off, end - start);
2773         }
2774 
drawText(Canvas c, int start, int end, float x, float y, Paint p)2775         public void drawText(Canvas c, int start, int end,
2776                              float x, float y, Paint p) {
2777             c.drawText(mChars, start + mStart, end - start, x, y, p);
2778         }
2779 
measureText(int start, int end, Paint p)2780         public float measureText(int start, int end, Paint p) {
2781             return p.measureText(mChars, start + mStart, end - start);
2782         }
2783 
getTextWidths(int start, int end, float[] widths, Paint p)2784         public int getTextWidths(int start, int end, float[] widths, Paint p) {
2785             return p.getTextWidths(mChars, start + mStart, end - start, widths);
2786         }
2787     }
2788 
2789     /**
2790      * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
2791      * except that the cursor position (if any) is retained in the new text.
2792      *
2793      * @see #setText(CharSequence, android.widget.TextView.BufferType)
2794      */
setTextKeepState(CharSequence text, BufferType type)2795     public final void setTextKeepState(CharSequence text, BufferType type) {
2796         int start = getSelectionStart();
2797         int end = getSelectionEnd();
2798         int len = text.length();
2799 
2800         setText(text, type);
2801 
2802         if (start >= 0 || end >= 0) {
2803             if (mText instanceof Spannable) {
2804                 Selection.setSelection((Spannable) mText,
2805                                        Math.max(0, Math.min(start, len)),
2806                                        Math.max(0, Math.min(end, len)));
2807             }
2808         }
2809     }
2810 
2811     @android.view.RemotableViewMethod
setText(int resid)2812     public final void setText(int resid) {
2813         setText(getContext().getResources().getText(resid));
2814     }
2815 
setText(int resid, BufferType type)2816     public final void setText(int resid, BufferType type) {
2817         setText(getContext().getResources().getText(resid), type);
2818     }
2819 
2820     /**
2821      * Sets the text to be displayed when the text of the TextView is empty.
2822      * Null means to use the normal empty text. The hint does not currently
2823      * participate in determining the size of the view.
2824      *
2825      * This method is deprecated. Use {link #setHint(int, String)} or
2826      * {link #setHint(CharSequence, String)} instead.
2827      *
2828      * @attr ref android.R.styleable#TextView_hint
2829      */
2830     @android.view.RemotableViewMethod
setHint(CharSequence hint)2831     public final void setHint(CharSequence hint) {
2832         mHint = TextUtils.stringOrSpannedString(hint);
2833 
2834         if (mLayout != null) {
2835             checkForRelayout();
2836         }
2837 
2838         if (mText.length() == 0) {
2839             invalidate();
2840         }
2841     }
2842 
2843     /**
2844      * Sets the text to be displayed when the text of the TextView is empty,
2845      * from a resource.
2846      *
2847      * This method is deprecated. Use {link #setHint(int, String)} or
2848      * {link #setHint(CharSequence, String)} instead.
2849      *
2850      * @attr ref android.R.styleable#TextView_hint
2851      */
2852     @android.view.RemotableViewMethod
setHint(int resid)2853     public final void setHint(int resid) {
2854         setHint(getContext().getResources().getText(resid));
2855     }
2856 
2857     /**
2858      * Returns the hint that is displayed when the text of the TextView
2859      * is empty.
2860      *
2861      * @attr ref android.R.styleable#TextView_hint
2862      */
2863     @ViewDebug.CapturedViewProperty
getHint()2864     public CharSequence getHint() {
2865         return mHint;
2866     }
2867 
2868     /**
2869      * Set the type of the content with a constant as defined for
2870      * {@link EditorInfo#inputType}.  This will take care of changing
2871      * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
2872      * match the given content type.  If the given content type is
2873      * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
2874      * not be displayed for this text view.
2875      *
2876      * @see #getInputType()
2877      * @see #setRawInputType(int)
2878      * @see android.text.InputType
2879      * @attr ref android.R.styleable#TextView_inputType
2880      */
setInputType(int type)2881     public void setInputType(int type) {
2882         final boolean wasPassword = isPasswordInputType(mInputType);
2883         final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
2884         setInputType(type, false);
2885         final boolean isPassword = isPasswordInputType(type);
2886         final boolean isVisiblePassword = isVisiblePasswordInputType(type);
2887         boolean forceUpdate = false;
2888         if (isPassword) {
2889             setTransformationMethod(PasswordTransformationMethod.getInstance());
2890             setTypefaceByIndex(MONOSPACE, 0);
2891         } else if (isVisiblePassword) {
2892             if (mTransformation == PasswordTransformationMethod.getInstance()) {
2893                 forceUpdate = true;
2894             }
2895             setTypefaceByIndex(MONOSPACE, 0);
2896         } else if (wasPassword || wasVisiblePassword) {
2897             // not in password mode, clean up typeface and transformation
2898             setTypefaceByIndex(-1, -1);
2899             if (mTransformation == PasswordTransformationMethod.getInstance()) {
2900                 forceUpdate = true;
2901             }
2902         }
2903 
2904         boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
2905                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
2906                 (EditorInfo.TYPE_CLASS_TEXT
2907                         | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
2908 
2909         // We need to update the single line mode if it has changed or we
2910         // were previously in password mode.
2911         if (mSingleLine == multiLine || forceUpdate) {
2912             // Change single line mode, but only change the transformation if
2913             // we are not in password mode.
2914             applySingleLine(!multiLine, !isPassword);
2915         }
2916 
2917         InputMethodManager imm = InputMethodManager.peekInstance();
2918         if (imm != null) imm.restartInput(this);
2919     }
2920 
isPasswordInputType(int inputType)2921     private boolean isPasswordInputType(int inputType) {
2922         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2923                 | EditorInfo.TYPE_MASK_VARIATION);
2924         return variation
2925                 == (EditorInfo.TYPE_CLASS_TEXT
2926                         | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
2927     }
2928 
isVisiblePasswordInputType(int inputType)2929     private boolean isVisiblePasswordInputType(int inputType) {
2930         final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS
2931                 | EditorInfo.TYPE_MASK_VARIATION);
2932         return variation
2933                 == (EditorInfo.TYPE_CLASS_TEXT
2934                         | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
2935     }
2936 
2937     /**
2938      * Directly change the content type integer of the text view, without
2939      * modifying any other state.
2940      * @see #setInputType(int)
2941      * @see android.text.InputType
2942      * @attr ref android.R.styleable#TextView_inputType
2943      */
setRawInputType(int type)2944     public void setRawInputType(int type) {
2945         mInputType = type;
2946     }
2947 
setInputType(int type, boolean direct)2948     private void setInputType(int type, boolean direct) {
2949         final int cls = type & EditorInfo.TYPE_MASK_CLASS;
2950         KeyListener input;
2951         if (cls == EditorInfo.TYPE_CLASS_TEXT) {
2952             boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
2953                     != 0;
2954             TextKeyListener.Capitalize cap;
2955             if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
2956                 cap = TextKeyListener.Capitalize.CHARACTERS;
2957             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
2958                 cap = TextKeyListener.Capitalize.WORDS;
2959             } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
2960                 cap = TextKeyListener.Capitalize.SENTENCES;
2961             } else {
2962                 cap = TextKeyListener.Capitalize.NONE;
2963             }
2964             input = TextKeyListener.getInstance(autotext, cap);
2965         } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
2966             input = DigitsKeyListener.getInstance(
2967                     (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
2968                     (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
2969         } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
2970             switch (type & EditorInfo.TYPE_MASK_VARIATION) {
2971                 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
2972                     input = DateKeyListener.getInstance();
2973                     break;
2974                 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
2975                     input = TimeKeyListener.getInstance();
2976                     break;
2977                 default:
2978                     input = DateTimeKeyListener.getInstance();
2979                     break;
2980             }
2981         } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
2982             input = DialerKeyListener.getInstance();
2983         } else {
2984             input = TextKeyListener.getInstance();
2985         }
2986         mInputType = type;
2987         if (direct) mInput = input;
2988         else {
2989             setKeyListenerOnly(input);
2990         }
2991     }
2992 
2993     /**
2994      * Get the type of the content.
2995      *
2996      * @see #setInputType(int)
2997      * @see android.text.InputType
2998      */
getInputType()2999     public int getInputType() {
3000         return mInputType;
3001     }
3002 
3003     /**
3004      * Change the editor type integer associated with the text view, which
3005      * will be reported to an IME with {@link EditorInfo#imeOptions} when it
3006      * has focus.
3007      * @see #getImeOptions
3008      * @see android.view.inputmethod.EditorInfo
3009      * @attr ref android.R.styleable#TextView_imeOptions
3010      */
setImeOptions(int imeOptions)3011     public void setImeOptions(int imeOptions) {
3012         if (mInputContentType == null) {
3013             mInputContentType = new InputContentType();
3014         }
3015         mInputContentType.imeOptions = imeOptions;
3016     }
3017 
3018     /**
3019      * Get the type of the IME editor.
3020      *
3021      * @see #setImeOptions(int)
3022      * @see android.view.inputmethod.EditorInfo
3023      */
getImeOptions()3024     public int getImeOptions() {
3025         return mInputContentType != null
3026                 ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
3027     }
3028 
3029     /**
3030      * Change the custom IME action associated with the text view, which
3031      * will be reported to an IME with {@link EditorInfo#actionLabel}
3032      * and {@link EditorInfo#actionId} when it has focus.
3033      * @see #getImeActionLabel
3034      * @see #getImeActionId
3035      * @see android.view.inputmethod.EditorInfo
3036      * @attr ref android.R.styleable#TextView_imeActionLabel
3037      * @attr ref android.R.styleable#TextView_imeActionId
3038      */
setImeActionLabel(CharSequence label, int actionId)3039     public void setImeActionLabel(CharSequence label, int actionId) {
3040         if (mInputContentType == null) {
3041             mInputContentType = new InputContentType();
3042         }
3043         mInputContentType.imeActionLabel = label;
3044         mInputContentType.imeActionId = actionId;
3045     }
3046 
3047     /**
3048      * Get the IME action label previous set with {@link #setImeActionLabel}.
3049      *
3050      * @see #setImeActionLabel
3051      * @see android.view.inputmethod.EditorInfo
3052      */
getImeActionLabel()3053     public CharSequence getImeActionLabel() {
3054         return mInputContentType != null
3055                 ? mInputContentType.imeActionLabel : null;
3056     }
3057 
3058     /**
3059      * Get the IME action ID previous set with {@link #setImeActionLabel}.
3060      *
3061      * @see #setImeActionLabel
3062      * @see android.view.inputmethod.EditorInfo
3063      */
getImeActionId()3064     public int getImeActionId() {
3065         return mInputContentType != null
3066                 ? mInputContentType.imeActionId : 0;
3067     }
3068 
3069     /**
3070      * Set a special listener to be called when an action is performed
3071      * on the text view.  This will be called when the enter key is pressed,
3072      * or when an action supplied to the IME is selected by the user.  Setting
3073      * this means that the normal hard key event will not insert a newline
3074      * into the text view, even if it is multi-line; holding down the ALT
3075      * modifier will, however, allow the user to insert a newline character.
3076      */
setOnEditorActionListener(OnEditorActionListener l)3077     public void setOnEditorActionListener(OnEditorActionListener l) {
3078         if (mInputContentType == null) {
3079             mInputContentType = new InputContentType();
3080         }
3081         mInputContentType.onEditorActionListener = l;
3082     }
3083 
3084     /**
3085      * Called when an attached input method calls
3086      * {@link InputConnection#performEditorAction(int)
3087      * InputConnection.performEditorAction()}
3088      * for this text view.  The default implementation will call your action
3089      * listener supplied to {@link #setOnEditorActionListener}, or perform
3090      * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
3091      * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE
3092      * EditorInfo.IME_ACTION_DONE}.
3093      *
3094      * <p>For backwards compatibility, if no IME options have been set and the
3095      * text view would not normally advance focus on enter, then
3096      * the NEXT and DONE actions received here will be turned into an enter
3097      * key down/up pair to go through the normal key handling.
3098      *
3099      * @param actionCode The code of the action being performed.
3100      *
3101      * @see #setOnEditorActionListener
3102      */
onEditorAction(int actionCode)3103     public void onEditorAction(int actionCode) {
3104         final InputContentType ict = mInputContentType;
3105         if (ict != null) {
3106             if (ict.onEditorActionListener != null) {
3107                 if (ict.onEditorActionListener.onEditorAction(this,
3108                         actionCode, null)) {
3109                     return;
3110                 }
3111             }
3112 
3113             // This is the handling for some default action.
3114             // Note that for backwards compatibility we don't do this
3115             // default handling if explicit ime options have not been given,
3116             // instead turning this into the normal enter key codes that an
3117             // app may be expecting.
3118             if (actionCode == EditorInfo.IME_ACTION_NEXT) {
3119                 View v = focusSearch(FOCUS_DOWN);
3120                 if (v != null) {
3121                     if (!v.requestFocus(FOCUS_DOWN)) {
3122                         throw new IllegalStateException("focus search returned a view " +
3123                                 "that wasn't able to take focus!");
3124                     }
3125                 }
3126                 return;
3127 
3128             } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
3129                 InputMethodManager imm = InputMethodManager.peekInstance();
3130                 if (imm != null) {
3131                     imm.hideSoftInputFromWindow(getWindowToken(), 0);
3132                 }
3133                 return;
3134             }
3135         }
3136 
3137         Handler h = getHandler();
3138         if (h != null) {
3139             long eventTime = SystemClock.uptimeMillis();
3140             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3141                     new KeyEvent(eventTime, eventTime,
3142                     KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3143                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3144                     | KeyEvent.FLAG_EDITOR_ACTION)));
3145             h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
3146                     new KeyEvent(SystemClock.uptimeMillis(), eventTime,
3147                     KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
3148                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
3149                     | KeyEvent.FLAG_EDITOR_ACTION)));
3150         }
3151     }
3152 
3153     /**
3154      * Set the private content type of the text, which is the
3155      * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
3156      * field that will be filled in when creating an input connection.
3157      *
3158      * @see #getPrivateImeOptions()
3159      * @see EditorInfo#privateImeOptions
3160      * @attr ref android.R.styleable#TextView_privateImeOptions
3161      */
setPrivateImeOptions(String type)3162     public void setPrivateImeOptions(String type) {
3163         if (mInputContentType == null) mInputContentType = new InputContentType();
3164         mInputContentType.privateImeOptions = type;
3165     }
3166 
3167     /**
3168      * Get the private type of the content.
3169      *
3170      * @see #setPrivateImeOptions(String)
3171      * @see EditorInfo#privateImeOptions
3172      */
getPrivateImeOptions()3173     public String getPrivateImeOptions() {
3174         return mInputContentType != null
3175                 ? mInputContentType.privateImeOptions : null;
3176     }
3177 
3178     /**
3179      * Set the extra input data of the text, which is the
3180      * {@link EditorInfo#extras TextBoxAttribute.extras}
3181      * Bundle that will be filled in when creating an input connection.  The
3182      * given integer is the resource ID of an XML resource holding an
3183      * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
3184      *
3185      * @see #getInputExtras(boolean)
3186      * @see EditorInfo#extras
3187      * @attr ref android.R.styleable#TextView_editorExtras
3188      */
setInputExtras(int xmlResId)3189     public void setInputExtras(int xmlResId)
3190             throws XmlPullParserException, IOException {
3191         XmlResourceParser parser = getResources().getXml(xmlResId);
3192         if (mInputContentType == null) mInputContentType = new InputContentType();
3193         mInputContentType.extras = new Bundle();
3194         getResources().parseBundleExtras(parser, mInputContentType.extras);
3195     }
3196 
3197     /**
3198      * Retrieve the input extras currently associated with the text view, which
3199      * can be viewed as well as modified.
3200      *
3201      * @param create If true, the extras will be created if they don't already
3202      * exist.  Otherwise, null will be returned if none have been created.
3203      * @see #setInputExtras(int)View
3204      * @see EditorInfo#extras
3205      * @attr ref android.R.styleable#TextView_editorExtras
3206      */
getInputExtras(boolean create)3207     public Bundle getInputExtras(boolean create) {
3208         if (mInputContentType == null) {
3209             if (!create) return null;
3210             mInputContentType = new InputContentType();
3211         }
3212         if (mInputContentType.extras == null) {
3213             if (!create) return null;
3214             mInputContentType.extras = new Bundle();
3215         }
3216         return mInputContentType.extras;
3217     }
3218 
3219     /**
3220      * Returns the error message that was set to be displayed with
3221      * {@link #setError}, or <code>null</code> if no error was set
3222      * or if it the error was cleared by the widget after user input.
3223      */
getError()3224     public CharSequence getError() {
3225         return mError;
3226     }
3227 
3228     /**
3229      * Sets the right-hand compound drawable of the TextView to the "error"
3230      * icon and sets an error message that will be displayed in a popup when
3231      * the TextView has focus.  The icon and error message will be reset to
3232      * null when any key events cause changes to the TextView's text.  If the
3233      * <code>error</code> is <code>null</code>, the error message and icon
3234      * will be cleared.
3235      */
3236     @android.view.RemotableViewMethod
setError(CharSequence error)3237     public void setError(CharSequence error) {
3238         if (error == null) {
3239             setError(null, null);
3240         } else {
3241             Drawable dr = getContext().getResources().
3242                 getDrawable(com.android.internal.R.drawable.
3243                             indicator_input_error);
3244 
3245             dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
3246             setError(error, dr);
3247         }
3248     }
3249 
3250     /**
3251      * Sets the right-hand compound drawable of the TextView to the specified
3252      * icon and sets an error message that will be displayed in a popup when
3253      * the TextView has focus.  The icon and error message will be reset to
3254      * null when any key events cause changes to the TextView's text.  The
3255      * drawable must already have had {@link Drawable#setBounds} set on it.
3256      * If the <code>error</code> is <code>null</code>, the error message will
3257      * be cleared (and you should provide a <code>null</code> icon as well).
3258      */
setError(CharSequence error, Drawable icon)3259     public void setError(CharSequence error, Drawable icon) {
3260         error = TextUtils.stringOrSpannedString(error);
3261 
3262         mError = error;
3263         mErrorWasChanged = true;
3264         final Drawables dr = mDrawables;
3265         if (dr != null) {
3266             setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
3267                                  icon, dr.mDrawableBottom);
3268         } else {
3269             setCompoundDrawables(null, null, icon, null);
3270         }
3271 
3272         if (error == null) {
3273             if (mPopup != null) {
3274                 if (mPopup.isShowing()) {
3275                     mPopup.dismiss();
3276                 }
3277 
3278                 mPopup = null;
3279             }
3280         } else {
3281             if (isFocused()) {
3282                 showError();
3283             }
3284         }
3285     }
3286 
showError()3287     private void showError() {
3288         if (getWindowToken() == null) {
3289             mShowErrorAfterAttach = true;
3290             return;
3291         }
3292 
3293         if (mPopup == null) {
3294             LayoutInflater inflater = LayoutInflater.from(getContext());
3295             final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
3296                     null);
3297 
3298             final float scale = getResources().getDisplayMetrics().density;
3299             mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f),
3300                     (int) (50 * scale + 0.5f));
3301             mPopup.setFocusable(false);
3302             // The user is entering text, so the input method is needed.  We
3303             // don't want the popup to be displayed on top of it.
3304             mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
3305         }
3306 
3307         TextView tv = (TextView) mPopup.getContentView();
3308         chooseSize(mPopup, mError, tv);
3309         tv.setText(mError);
3310 
3311         mPopup.showAsDropDown(this, getErrorX(), getErrorY());
3312         mPopup.fixDirection(mPopup.isAboveAnchor());
3313     }
3314 
3315     private static class ErrorPopup extends PopupWindow {
3316         private boolean mAbove = false;
3317         private TextView mView;
3318 
ErrorPopup(TextView v, int width, int height)3319         ErrorPopup(TextView v, int width, int height) {
3320             super(v, width, height);
3321             mView = v;
3322         }
3323 
fixDirection(boolean above)3324         void fixDirection(boolean above) {
3325             mAbove = above;
3326 
3327             if (above) {
3328                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
3329             } else {
3330                 mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
3331             }
3332         }
3333 
3334         @Override
update(int x, int y, int w, int h, boolean force)3335         public void update(int x, int y, int w, int h, boolean force) {
3336             super.update(x, y, w, h, force);
3337 
3338             boolean above = isAboveAnchor();
3339             if (above != mAbove) {
3340                 fixDirection(above);
3341             }
3342         }
3343     }
3344 
3345     /**
3346      * Returns the Y offset to make the pointy top of the error point
3347      * at the middle of the error icon.
3348      */
getErrorX()3349     private int getErrorX() {
3350         /*
3351          * The "25" is the distance between the point and the right edge
3352          * of the background
3353          */
3354         final float scale = getResources().getDisplayMetrics().density;
3355 
3356         final Drawables dr = mDrawables;
3357         return getWidth() - mPopup.getWidth()
3358                 - getPaddingRight()
3359                 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
3360     }
3361 
3362     /**
3363      * Returns the Y offset to make the pointy top of the error point
3364      * at the bottom of the error icon.
3365      */
getErrorY()3366     private int getErrorY() {
3367         /*
3368          * Compound, not extended, because the icon is not clipped
3369          * if the text height is smaller.
3370          */
3371         int vspace = mBottom - mTop -
3372                      getCompoundPaddingBottom() - getCompoundPaddingTop();
3373 
3374         final Drawables dr = mDrawables;
3375         int icontop = getCompoundPaddingTop()
3376                 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
3377 
3378         /*
3379          * The "2" is the distance between the point and the top edge
3380          * of the background.
3381          */
3382 
3383         return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
3384                 - getHeight() - 2;
3385     }
3386 
hideError()3387     private void hideError() {
3388         if (mPopup != null) {
3389             if (mPopup.isShowing()) {
3390                 mPopup.dismiss();
3391             }
3392         }
3393 
3394         mShowErrorAfterAttach = false;
3395     }
3396 
chooseSize(PopupWindow pop, CharSequence text, TextView tv)3397     private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
3398         int wid = tv.getPaddingLeft() + tv.getPaddingRight();
3399         int ht = tv.getPaddingTop() + tv.getPaddingBottom();
3400 
3401         /*
3402          * Figure out how big the text would be if we laid it out to the
3403          * full width of this view minus the border.
3404          */
3405         int cap = getWidth() - wid;
3406         if (cap < 0) {
3407             cap = 200; // We must not be measured yet -- setFrame() will fix it.
3408         }
3409 
3410         Layout l = new StaticLayout(text, tv.getPaint(), cap,
3411                                     Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
3412         float max = 0;
3413         for (int i = 0; i < l.getLineCount(); i++) {
3414             max = Math.max(max, l.getLineWidth(i));
3415         }
3416 
3417         /*
3418          * Now set the popup size to be big enough for the text plus the border.
3419          */
3420         pop.setWidth(wid + (int) Math.ceil(max));
3421         pop.setHeight(ht + l.getHeight());
3422     }
3423 
3424 
3425     @Override
setFrame(int l, int t, int r, int b)3426     protected boolean setFrame(int l, int t, int r, int b) {
3427         boolean result = super.setFrame(l, t, r, b);
3428 
3429         if (mPopup != null) {
3430             TextView tv = (TextView) mPopup.getContentView();
3431             chooseSize(mPopup, mError, tv);
3432             mPopup.update(this, getErrorX(), getErrorY(),
3433                           mPopup.getWidth(), mPopup.getHeight());
3434         }
3435 
3436         restartMarqueeIfNeeded();
3437 
3438         return result;
3439     }
3440 
restartMarqueeIfNeeded()3441     private void restartMarqueeIfNeeded() {
3442         if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3443             mRestartMarquee = false;
3444             startMarquee();
3445         }
3446     }
3447 
3448     /**
3449      * Sets the list of input filters that will be used if the buffer is
3450      * Editable.  Has no effect otherwise.
3451      *
3452      * @attr ref android.R.styleable#TextView_maxLength
3453      */
setFilters(InputFilter[] filters)3454     public void setFilters(InputFilter[] filters) {
3455         if (filters == null) {
3456             throw new IllegalArgumentException();
3457         }
3458 
3459         mFilters = filters;
3460 
3461         if (mText instanceof Editable) {
3462             setFilters((Editable) mText, filters);
3463         }
3464     }
3465 
3466     /**
3467      * Sets the list of input filters on the specified Editable,
3468      * and includes mInput in the list if it is an InputFilter.
3469      */
setFilters(Editable e, InputFilter[] filters)3470     private void setFilters(Editable e, InputFilter[] filters) {
3471         if (mInput instanceof InputFilter) {
3472             InputFilter[] nf = new InputFilter[filters.length + 1];
3473 
3474             System.arraycopy(filters, 0, nf, 0, filters.length);
3475             nf[filters.length] = (InputFilter) mInput;
3476 
3477             e.setFilters(nf);
3478         } else {
3479             e.setFilters(filters);
3480         }
3481     }
3482 
3483     /**
3484      * Returns the current list of input filters.
3485      */
getFilters()3486     public InputFilter[] getFilters() {
3487         return mFilters;
3488     }
3489 
3490     /////////////////////////////////////////////////////////////////////////
3491 
getVerticalOffset(boolean forceNormal)3492     private int getVerticalOffset(boolean forceNormal) {
3493         int voffset = 0;
3494         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3495 
3496         Layout l = mLayout;
3497         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3498             l = mHintLayout;
3499         }
3500 
3501         if (gravity != Gravity.TOP) {
3502             int boxht;
3503 
3504             if (l == mHintLayout) {
3505                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3506                         getCompoundPaddingBottom();
3507             } else {
3508                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3509                         getExtendedPaddingBottom();
3510             }
3511             int textht = l.getHeight();
3512 
3513             if (textht < boxht) {
3514                 if (gravity == Gravity.BOTTOM)
3515                     voffset = boxht - textht;
3516                 else // (gravity == Gravity.CENTER_VERTICAL)
3517                     voffset = (boxht - textht) >> 1;
3518             }
3519         }
3520         return voffset;
3521     }
3522 
getBottomVerticalOffset(boolean forceNormal)3523     private int getBottomVerticalOffset(boolean forceNormal) {
3524         int voffset = 0;
3525         final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
3526 
3527         Layout l = mLayout;
3528         if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
3529             l = mHintLayout;
3530         }
3531 
3532         if (gravity != Gravity.BOTTOM) {
3533             int boxht;
3534 
3535             if (l == mHintLayout) {
3536                 boxht = getMeasuredHeight() - getCompoundPaddingTop() -
3537                         getCompoundPaddingBottom();
3538             } else {
3539                 boxht = getMeasuredHeight() - getExtendedPaddingTop() -
3540                         getExtendedPaddingBottom();
3541             }
3542             int textht = l.getHeight();
3543 
3544             if (textht < boxht) {
3545                 if (gravity == Gravity.TOP)
3546                     voffset = boxht - textht;
3547                 else // (gravity == Gravity.CENTER_VERTICAL)
3548                     voffset = (boxht - textht) >> 1;
3549             }
3550         }
3551         return voffset;
3552     }
3553 
invalidateCursorPath()3554     private void invalidateCursorPath() {
3555         if (mHighlightPathBogus) {
3556             invalidateCursor();
3557         } else {
3558             synchronized (sTempRect) {
3559                 /*
3560                  * The reason for this concern about the thickness of the
3561                  * cursor and doing the floor/ceil on the coordinates is that
3562                  * some EditTexts (notably textfields in the Browser) have
3563                  * anti-aliased text where not all the characters are
3564                  * necessarily at integer-multiple locations.  This should
3565                  * make sure the entire cursor gets invalidated instead of
3566                  * sometimes missing half a pixel.
3567                  */
3568 
3569                 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
3570                 if (thick < 1.0f) {
3571                     thick = 1.0f;
3572                 }
3573 
3574                 thick /= 2;
3575 
3576                 mHighlightPath.computeBounds(sTempRect, false);
3577 
3578                 int left = getCompoundPaddingLeft();
3579                 int top = getExtendedPaddingTop() + getVerticalOffset(true);
3580 
3581                 invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
3582                            (int) FloatMath.floor(top + sTempRect.top - thick),
3583                            (int) FloatMath.ceil(left + sTempRect.right + thick),
3584                            (int) FloatMath.ceil(top + sTempRect.bottom + thick));
3585             }
3586         }
3587     }
3588 
invalidateCursor()3589     private void invalidateCursor() {
3590         int where = Selection.getSelectionEnd(mText);
3591 
3592         invalidateCursor(where, where, where);
3593     }
3594 
invalidateCursor(int a, int b, int c)3595     private void invalidateCursor(int a, int b, int c) {
3596         if (mLayout == null) {
3597             invalidate();
3598         } else {
3599             if (a >= 0 || b >= 0 || c >= 0) {
3600                 int first = Math.min(Math.min(a, b), c);
3601                 int last = Math.max(Math.max(a, b), c);
3602 
3603                 int line = mLayout.getLineForOffset(first);
3604                 int top = mLayout.getLineTop(line);
3605 
3606                 // This is ridiculous, but the descent from the line above
3607                 // can hang down into the line we really want to redraw,
3608                 // so we have to invalidate part of the line above to make
3609                 // sure everything that needs to be redrawn really is.
3610                 // (But not the whole line above, because that would cause
3611                 // the same problem with the descenders on the line above it!)
3612                 if (line > 0) {
3613                     top -= mLayout.getLineDescent(line - 1);
3614                 }
3615 
3616                 int line2;
3617 
3618                 if (first == last)
3619                     line2 = line;
3620                 else
3621                     line2 = mLayout.getLineForOffset(last);
3622 
3623                 int bottom = mLayout.getLineTop(line2 + 1);
3624                 int voffset = getVerticalOffset(true);
3625 
3626                 int left = getCompoundPaddingLeft() + mScrollX;
3627                 invalidate(left, top + voffset + getExtendedPaddingTop(),
3628                            left + getWidth() - getCompoundPaddingLeft() -
3629                            getCompoundPaddingRight(),
3630                            bottom + voffset + getExtendedPaddingTop());
3631             }
3632         }
3633     }
3634 
registerForPreDraw()3635     private void registerForPreDraw() {
3636         final ViewTreeObserver observer = getViewTreeObserver();
3637         if (observer == null) {
3638             return;
3639         }
3640 
3641         if (mPreDrawState == PREDRAW_NOT_REGISTERED) {
3642             observer.addOnPreDrawListener(this);
3643             mPreDrawState = PREDRAW_PENDING;
3644         } else if (mPreDrawState == PREDRAW_DONE) {
3645             mPreDrawState = PREDRAW_PENDING;
3646         }
3647 
3648         // else state is PREDRAW_PENDING, so keep waiting.
3649     }
3650 
3651     /**
3652      * {@inheritDoc}
3653      */
onPreDraw()3654     public boolean onPreDraw() {
3655         if (mPreDrawState != PREDRAW_PENDING) {
3656             return true;
3657         }
3658 
3659         if (mLayout == null) {
3660             assumeLayout();
3661         }
3662 
3663         boolean changed = false;
3664 
3665         if (mMovement != null) {
3666             int curs = Selection.getSelectionEnd(mText);
3667 
3668             /*
3669              * TODO: This should really only keep the end in view if
3670              * it already was before the text changed.  I'm not sure
3671              * of a good way to tell from here if it was.
3672              */
3673             if (curs < 0 &&
3674                   (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
3675                 curs = mText.length();
3676             }
3677 
3678             if (curs >= 0) {
3679                 changed = bringPointIntoView(curs);
3680             }
3681         } else {
3682             changed = bringTextIntoView();
3683         }
3684 
3685         mPreDrawState = PREDRAW_DONE;
3686         return !changed;
3687     }
3688 
3689     @Override
onAttachedToWindow()3690     protected void onAttachedToWindow() {
3691         super.onAttachedToWindow();
3692 
3693         mTemporaryDetach = false;
3694 
3695         if (mShowErrorAfterAttach) {
3696             showError();
3697             mShowErrorAfterAttach = false;
3698         }
3699     }
3700 
3701     @Override
onDetachedFromWindow()3702     protected void onDetachedFromWindow() {
3703         super.onDetachedFromWindow();
3704 
3705         if (mPreDrawState != PREDRAW_NOT_REGISTERED) {
3706             final ViewTreeObserver observer = getViewTreeObserver();
3707             if (observer != null) {
3708                 observer.removeOnPreDrawListener(this);
3709                 mPreDrawState = PREDRAW_NOT_REGISTERED;
3710             }
3711         }
3712 
3713         if (mError != null) {
3714             hideError();
3715         }
3716     }
3717 
3718     @Override
isPaddingOffsetRequired()3719     protected boolean isPaddingOffsetRequired() {
3720         return mShadowRadius != 0 || mDrawables != null;
3721     }
3722 
3723     @Override
getLeftPaddingOffset()3724     protected int getLeftPaddingOffset() {
3725         return getCompoundPaddingLeft() - mPaddingLeft +
3726                 (int) Math.min(0, mShadowDx - mShadowRadius);
3727     }
3728 
3729     @Override
getTopPaddingOffset()3730     protected int getTopPaddingOffset() {
3731         return (int) Math.min(0, mShadowDy - mShadowRadius);
3732     }
3733 
3734     @Override
getBottomPaddingOffset()3735     protected int getBottomPaddingOffset() {
3736         return (int) Math.max(0, mShadowDy + mShadowRadius);
3737     }
3738 
3739     @Override
getRightPaddingOffset()3740     protected int getRightPaddingOffset() {
3741         return -(getCompoundPaddingRight() - mPaddingRight) +
3742                 (int) Math.max(0, mShadowDx + mShadowRadius);
3743     }
3744 
3745     @Override
verifyDrawable(Drawable who)3746     protected boolean verifyDrawable(Drawable who) {
3747         final boolean verified = super.verifyDrawable(who);
3748         if (!verified && mDrawables != null) {
3749             return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
3750                     who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom;
3751         }
3752         return verified;
3753     }
3754 
3755     @Override
invalidateDrawable(Drawable drawable)3756     public void invalidateDrawable(Drawable drawable) {
3757         if (verifyDrawable(drawable)) {
3758             final Rect dirty = drawable.getBounds();
3759             int scrollX = mScrollX;
3760             int scrollY = mScrollY;
3761 
3762             // IMPORTANT: The coordinates below are based on the coordinates computed
3763             // for each compound drawable in onDraw(). Make sure to update each section
3764             // accordingly.
3765             final TextView.Drawables drawables = mDrawables;
3766             if (drawables != null) {
3767                 if (drawable == drawables.mDrawableLeft) {
3768                     final int compoundPaddingTop = getCompoundPaddingTop();
3769                     final int compoundPaddingBottom = getCompoundPaddingBottom();
3770                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3771 
3772                     scrollX += mPaddingLeft;
3773                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
3774                 } else if (drawable == drawables.mDrawableRight) {
3775                     final int compoundPaddingTop = getCompoundPaddingTop();
3776                     final int compoundPaddingBottom = getCompoundPaddingBottom();
3777                     final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
3778 
3779                     scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
3780                     scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
3781                 } else if (drawable == drawables.mDrawableTop) {
3782                     final int compoundPaddingLeft = getCompoundPaddingLeft();
3783                     final int compoundPaddingRight = getCompoundPaddingRight();
3784                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3785 
3786                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
3787                     scrollY += mPaddingTop;
3788                 } else if (drawable == drawables.mDrawableBottom) {
3789                     final int compoundPaddingLeft = getCompoundPaddingLeft();
3790                     final int compoundPaddingRight = getCompoundPaddingRight();
3791                     final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
3792 
3793                     scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
3794                     scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
3795                 }
3796             }
3797 
3798             invalidate(dirty.left + scrollX, dirty.top + scrollY,
3799                     dirty.right + scrollX, dirty.bottom + scrollY);
3800         }
3801     }
3802 
3803     @Override
onDraw(Canvas canvas)3804     protected void onDraw(Canvas canvas) {
3805         restartMarqueeIfNeeded();
3806 
3807         // Draw the background for this view
3808         super.onDraw(canvas);
3809 
3810         final int compoundPaddingLeft = getCompoundPaddingLeft();
3811         final int compoundPaddingTop = getCompoundPaddingTop();
3812         final int compoundPaddingRight = getCompoundPaddingRight();
3813         final int compoundPaddingBottom = getCompoundPaddingBottom();
3814         final int scrollX = mScrollX;
3815         final int scrollY = mScrollY;
3816         final int right = mRight;
3817         final int left = mLeft;
3818         final int bottom = mBottom;
3819         final int top = mTop;
3820 
3821         final Drawables dr = mDrawables;
3822         if (dr != null) {
3823             /*
3824              * Compound, not extended, because the icon is not clipped
3825              * if the text height is smaller.
3826              */
3827 
3828             int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
3829             int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
3830 
3831             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3832             // Make sure to update invalidateDrawable() when changing this code.
3833             if (dr.mDrawableLeft != null) {
3834                 canvas.save();
3835                 canvas.translate(scrollX + mPaddingLeft,
3836                                  scrollY + compoundPaddingTop +
3837                                  (vspace - dr.mDrawableHeightLeft) / 2);
3838                 dr.mDrawableLeft.draw(canvas);
3839                 canvas.restore();
3840             }
3841 
3842             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3843             // Make sure to update invalidateDrawable() when changing this code.
3844             if (dr.mDrawableRight != null) {
3845                 canvas.save();
3846                 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
3847                          scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
3848                 dr.mDrawableRight.draw(canvas);
3849                 canvas.restore();
3850             }
3851 
3852             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3853             // Make sure to update invalidateDrawable() when changing this code.
3854             if (dr.mDrawableTop != null) {
3855                 canvas.save();
3856                 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
3857                         scrollY + mPaddingTop);
3858                 dr.mDrawableTop.draw(canvas);
3859                 canvas.restore();
3860             }
3861 
3862             // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
3863             // Make sure to update invalidateDrawable() when changing this code.
3864             if (dr.mDrawableBottom != null) {
3865                 canvas.save();
3866                 canvas.translate(scrollX + compoundPaddingLeft +
3867                         (hspace - dr.mDrawableWidthBottom) / 2,
3868                          scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
3869                 dr.mDrawableBottom.draw(canvas);
3870                 canvas.restore();
3871             }
3872         }
3873 
3874         if (mPreDrawState == PREDRAW_DONE) {
3875             final ViewTreeObserver observer = getViewTreeObserver();
3876             if (observer != null) {
3877                 observer.removeOnPreDrawListener(this);
3878                 mPreDrawState = PREDRAW_NOT_REGISTERED;
3879             }
3880         }
3881 
3882         int color = mCurTextColor;
3883 
3884         if (mLayout == null) {
3885             assumeLayout();
3886         }
3887 
3888         Layout layout = mLayout;
3889         int cursorcolor = color;
3890 
3891         if (mHint != null && mText.length() == 0) {
3892             if (mHintTextColor != null) {
3893                 color = mCurHintTextColor;
3894             }
3895 
3896             layout = mHintLayout;
3897         }
3898 
3899         mTextPaint.setColor(color);
3900         mTextPaint.drawableState = getDrawableState();
3901 
3902         canvas.save();
3903         /*  Would be faster if we didn't have to do this. Can we chop the
3904             (displayable) text so that we don't need to do this ever?
3905         */
3906 
3907         int extendedPaddingTop = getExtendedPaddingTop();
3908         int extendedPaddingBottom = getExtendedPaddingBottom();
3909 
3910         float clipLeft = compoundPaddingLeft + scrollX;
3911         float clipTop = extendedPaddingTop + scrollY;
3912         float clipRight = right - left - compoundPaddingRight + scrollX;
3913         float clipBottom = bottom - top - extendedPaddingBottom + scrollY;
3914 
3915         if (mShadowRadius != 0) {
3916             clipLeft += Math.min(0, mShadowDx - mShadowRadius);
3917             clipRight += Math.max(0, mShadowDx + mShadowRadius);
3918 
3919             clipTop += Math.min(0, mShadowDy - mShadowRadius);
3920             clipBottom += Math.max(0, mShadowDy + mShadowRadius);
3921         }
3922 
3923         canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
3924 
3925         int voffsetText = 0;
3926         int voffsetCursor = 0;
3927 
3928         // translate in by our padding
3929         {
3930             /* shortcircuit calling getVerticaOffset() */
3931             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
3932                 voffsetText = getVerticalOffset(false);
3933                 voffsetCursor = getVerticalOffset(true);
3934             }
3935             canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
3936         }
3937 
3938         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
3939             if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
3940                     (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
3941                 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
3942                         getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
3943             }
3944 
3945             if (mMarquee != null && mMarquee.isRunning()) {
3946                 canvas.translate(-mMarquee.mScroll, 0.0f);
3947             }
3948         }
3949 
3950         Path highlight = null;
3951         int selStart = -1, selEnd = -1;
3952 
3953         //  If there is no movement method, then there can be no selection.
3954         //  Check that first and attempt to skip everything having to do with
3955         //  the cursor.
3956         //  XXX This is not strictly true -- a program could set the
3957         //  selection manually if it really wanted to.
3958         if (mMovement != null && (isFocused() || isPressed())) {
3959             selStart = Selection.getSelectionStart(mText);
3960             selEnd = Selection.getSelectionEnd(mText);
3961 
3962             if (mCursorVisible && selStart >= 0 && isEnabled()) {
3963                 if (mHighlightPath == null)
3964                     mHighlightPath = new Path();
3965 
3966                 if (selStart == selEnd) {
3967                     if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
3968                         if (mHighlightPathBogus) {
3969                             mHighlightPath.reset();
3970                             mLayout.getCursorPath(selStart, mHighlightPath, mText);
3971                             mHighlightPathBogus = false;
3972                         }
3973 
3974                         // XXX should pass to skin instead of drawing directly
3975                         mHighlightPaint.setColor(cursorcolor);
3976                         mHighlightPaint.setStyle(Paint.Style.STROKE);
3977 
3978                         highlight = mHighlightPath;
3979                     }
3980                 } else {
3981                     if (mHighlightPathBogus) {
3982                         mHighlightPath.reset();
3983                         mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
3984                         mHighlightPathBogus = false;
3985                     }
3986 
3987                     // XXX should pass to skin instead of drawing directly
3988                     mHighlightPaint.setColor(mHighlightColor);
3989                     mHighlightPaint.setStyle(Paint.Style.FILL);
3990 
3991                     highlight = mHighlightPath;
3992                 }
3993             }
3994         }
3995 
3996         /*  Comment out until we decide what to do about animations
3997         boolean isLinearTextOn = false;
3998         if (currentTransformation != null) {
3999             isLinearTextOn = mTextPaint.isLinearTextOn();
4000             Matrix m = currentTransformation.getMatrix();
4001             if (!m.isIdentity()) {
4002                 // mTextPaint.setLinearTextOn(true);
4003             }
4004         }
4005         */
4006 
4007         final InputMethodState ims = mInputMethodState;
4008         if (ims != null && ims.mBatchEditNesting == 0) {
4009             InputMethodManager imm = InputMethodManager.peekInstance();
4010             if (imm != null) {
4011                 if (imm.isActive(this)) {
4012                     boolean reported = false;
4013                     if (ims.mContentChanged || ims.mSelectionModeChanged) {
4014                         // We are in extract mode and the content has changed
4015                         // in some way... just report complete new text to the
4016                         // input method.
4017                         reported = reportExtractedText();
4018                     }
4019                     if (!reported && highlight != null) {
4020                         int candStart = -1;
4021                         int candEnd = -1;
4022                         if (mText instanceof Spannable) {
4023                             Spannable sp = (Spannable)mText;
4024                             candStart = EditableInputConnection.getComposingSpanStart(sp);
4025                             candEnd = EditableInputConnection.getComposingSpanEnd(sp);
4026                         }
4027                         imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
4028                     }
4029                 }
4030 
4031                 if (imm.isWatchingCursor(this) && highlight != null) {
4032                     highlight.computeBounds(ims.mTmpRectF, true);
4033                     ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
4034 
4035                     canvas.getMatrix().mapPoints(ims.mTmpOffset);
4036                     ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
4037 
4038                     ims.mTmpRectF.offset(0, voffsetCursor - voffsetText);
4039 
4040                     ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
4041                             (int)(ims.mTmpRectF.top + 0.5),
4042                             (int)(ims.mTmpRectF.right + 0.5),
4043                             (int)(ims.mTmpRectF.bottom + 0.5));
4044 
4045                     imm.updateCursor(this,
4046                             ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
4047                             ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
4048                 }
4049             }
4050         }
4051 
4052         layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4053 
4054         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
4055             canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
4056             layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
4057         }
4058 
4059         /*  Comment out until we decide what to do about animations
4060         if (currentTransformation != null) {
4061             mTextPaint.setLinearTextOn(isLinearTextOn);
4062         }
4063         */
4064 
4065         canvas.restore();
4066     }
4067 
4068     @Override
getFocusedRect(Rect r)4069     public void getFocusedRect(Rect r) {
4070         if (mLayout == null) {
4071             super.getFocusedRect(r);
4072             return;
4073         }
4074 
4075         int sel = getSelectionEnd();
4076         if (sel < 0) {
4077             super.getFocusedRect(r);
4078             return;
4079         }
4080 
4081         int line = mLayout.getLineForOffset(sel);
4082         r.top = mLayout.getLineTop(line);
4083         r.bottom = mLayout.getLineBottom(line);
4084 
4085         r.left = (int) mLayout.getPrimaryHorizontal(sel);
4086         r.right = r.left + 1;
4087 
4088         // Adjust for padding and gravity.
4089         int paddingLeft = getCompoundPaddingLeft();
4090         int paddingTop = getExtendedPaddingTop();
4091         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4092             paddingTop += getVerticalOffset(false);
4093         }
4094         r.offset(paddingLeft, paddingTop);
4095     }
4096 
4097     /**
4098      * Return the number of lines of text, or 0 if the internal Layout has not
4099      * been built.
4100      */
getLineCount()4101     public int getLineCount() {
4102         return mLayout != null ? mLayout.getLineCount() : 0;
4103     }
4104 
4105     /**
4106      * Return the baseline for the specified line (0...getLineCount() - 1)
4107      * If bounds is not null, return the top, left, right, bottom extents
4108      * of the specified line in it. If the internal Layout has not been built,
4109      * return 0 and set bounds to (0, 0, 0, 0)
4110      * @param line which line to examine (0..getLineCount() - 1)
4111      * @param bounds Optional. If not null, it returns the extent of the line
4112      * @return the Y-coordinate of the baseline
4113      */
getLineBounds(int line, Rect bounds)4114     public int getLineBounds(int line, Rect bounds) {
4115         if (mLayout == null) {
4116             if (bounds != null) {
4117                 bounds.set(0, 0, 0, 0);
4118             }
4119             return 0;
4120         }
4121         else {
4122             int baseline = mLayout.getLineBounds(line, bounds);
4123 
4124             int voffset = getExtendedPaddingTop();
4125             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4126                 voffset += getVerticalOffset(true);
4127             }
4128             if (bounds != null) {
4129                 bounds.offset(getCompoundPaddingLeft(), voffset);
4130             }
4131             return baseline + voffset;
4132         }
4133     }
4134 
4135     @Override
getBaseline()4136     public int getBaseline() {
4137         if (mLayout == null) {
4138             return super.getBaseline();
4139         }
4140 
4141         int voffset = 0;
4142         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
4143             voffset = getVerticalOffset(true);
4144         }
4145 
4146         return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
4147     }
4148 
4149     @Override
onKeyDown(int keyCode, KeyEvent event)4150     public boolean onKeyDown(int keyCode, KeyEvent event) {
4151         int which = doKeyDown(keyCode, event, null);
4152         if (which == 0) {
4153             // Go through default dispatching.
4154             return super.onKeyDown(keyCode, event);
4155         }
4156 
4157         return true;
4158     }
4159 
4160     @Override
onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)4161     public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4162         KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
4163 
4164         int which = doKeyDown(keyCode, down, event);
4165         if (which == 0) {
4166             // Go through default dispatching.
4167             return super.onKeyMultiple(keyCode, repeatCount, event);
4168         }
4169         if (which == -1) {
4170             // Consumed the whole thing.
4171             return true;
4172         }
4173 
4174         repeatCount--;
4175 
4176         // We are going to dispatch the remaining events to either the input
4177         // or movement method.  To do this, we will just send a repeated stream
4178         // of down and up events until we have done the complete repeatCount.
4179         // It would be nice if those interfaces had an onKeyMultiple() method,
4180         // but adding that is a more complicated change.
4181         KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
4182         if (which == 1) {
4183             mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4184             while (--repeatCount > 0) {
4185                 mInput.onKeyDown(this, (Editable)mText, keyCode, down);
4186                 mInput.onKeyUp(this, (Editable)mText, keyCode, up);
4187             }
4188             if (mError != null && !mErrorWasChanged) {
4189                 setError(null, null);
4190             }
4191 
4192         } else if (which == 2) {
4193             mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4194             while (--repeatCount > 0) {
4195                 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
4196                 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
4197             }
4198         }
4199 
4200         return true;
4201     }
4202 
4203     /**
4204      * Returns true if pressing ENTER in this field advances focus instead
4205      * of inserting the character.  This is true mostly in single-line fields,
4206      * but also in mail addresses and subjects which will display on multiple
4207      * lines but where it doesn't make sense to insert newlines.
4208      */
shouldAdvanceFocusOnEnter()4209     private boolean shouldAdvanceFocusOnEnter() {
4210         if (mInput == null) {
4211             return false;
4212         }
4213 
4214         if (mSingleLine) {
4215             return true;
4216         }
4217 
4218         if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
4219             int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
4220 
4221             if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
4222                 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
4223                 return true;
4224             }
4225         }
4226 
4227         return false;
4228     }
4229 
doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)4230     private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
4231         if (!isEnabled()) {
4232             return 0;
4233         }
4234 
4235         switch (keyCode) {
4236             case KeyEvent.KEYCODE_ENTER:
4237                 // If ALT modifier is held, then we always insert a
4238                 // newline character.
4239                 if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) {
4240 
4241                     // When mInputContentType is set, we know that we are
4242                     // running in a "modern" cupcake environment, so don't need
4243                     // to worry about the application trying to capture
4244                     // enter key events.
4245                     if (mInputContentType != null) {
4246 
4247                         // If there is an action listener, given them a
4248                         // chance to consume the event.
4249                         if (mInputContentType.onEditorActionListener != null &&
4250                                 mInputContentType.onEditorActionListener.onEditorAction(
4251                                 this, EditorInfo.IME_NULL, event)) {
4252                             mInputContentType.enterDown = true;
4253                             // We are consuming the enter key for them.
4254                             return -1;
4255                         }
4256                     }
4257 
4258                     // If our editor should move focus when enter is pressed, or
4259                     // this is a generated event from an IME action button, then
4260                     // don't let it be inserted into the text.
4261                     if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4262                             || shouldAdvanceFocusOnEnter()) {
4263                         return -1;
4264                     }
4265                 }
4266                 break;
4267 
4268             case KeyEvent.KEYCODE_DPAD_CENTER:
4269                 if (shouldAdvanceFocusOnEnter()) {
4270                     return 0;
4271                 }
4272         }
4273 
4274         if (mInput != null) {
4275             /*
4276              * Keep track of what the error was before doing the input
4277              * so that if an input filter changed the error, we leave
4278              * that error showing.  Otherwise, we take down whatever
4279              * error was showing when the user types something.
4280              */
4281             mErrorWasChanged = false;
4282 
4283             boolean doDown = true;
4284             if (otherEvent != null) {
4285                 try {
4286                     beginBatchEdit();
4287                     boolean handled = mInput.onKeyOther(this, (Editable) mText,
4288                             otherEvent);
4289                     if (mError != null && !mErrorWasChanged) {
4290                         setError(null, null);
4291                     }
4292                     doDown = false;
4293                     if (handled) {
4294                         return -1;
4295                     }
4296                 } catch (AbstractMethodError e) {
4297                     // onKeyOther was added after 1.0, so if it isn't
4298                     // implemented we need to try to dispatch as a regular down.
4299                 } finally {
4300                     endBatchEdit();
4301                 }
4302             }
4303 
4304             if (doDown) {
4305                 beginBatchEdit();
4306                 if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) {
4307                     endBatchEdit();
4308                     if (mError != null && !mErrorWasChanged) {
4309                         setError(null, null);
4310                     }
4311                     return 1;
4312                 }
4313                 endBatchEdit();
4314             }
4315         }
4316 
4317         // bug 650865: sometimes we get a key event before a layout.
4318         // don't try to move around if we don't know the layout.
4319 
4320         if (mMovement != null && mLayout != null) {
4321             boolean doDown = true;
4322             if (otherEvent != null) {
4323                 try {
4324                     boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
4325                             otherEvent);
4326                     doDown = false;
4327                     if (handled) {
4328                         return -1;
4329                     }
4330                 } catch (AbstractMethodError e) {
4331                     // onKeyOther was added after 1.0, so if it isn't
4332                     // implemented we need to try to dispatch as a regular down.
4333                 }
4334             }
4335             if (doDown) {
4336                 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
4337                     return 2;
4338             }
4339         }
4340 
4341         return 0;
4342     }
4343 
4344     @Override
onKeyUp(int keyCode, KeyEvent event)4345     public boolean onKeyUp(int keyCode, KeyEvent event) {
4346         if (!isEnabled()) {
4347             return super.onKeyUp(keyCode, event);
4348         }
4349 
4350         switch (keyCode) {
4351             case KeyEvent.KEYCODE_DPAD_CENTER:
4352                 /*
4353                  * If there is a click listener, just call through to
4354                  * super, which will invoke it.
4355                  *
4356                  * If there isn't a click listener, try to show the soft
4357                  * input method.  (It will also
4358                  * call performClick(), but that won't do anything in
4359                  * this case.)
4360                  */
4361                 if (mOnClickListener == null) {
4362                     if (mMovement != null && mText instanceof Editable
4363                             && mLayout != null && onCheckIsTextEditor()) {
4364                         InputMethodManager imm = (InputMethodManager)
4365                                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
4366                         imm.showSoftInput(this, 0);
4367                     }
4368                 }
4369                 return super.onKeyUp(keyCode, event);
4370 
4371             case KeyEvent.KEYCODE_ENTER:
4372                 if (mInputContentType != null
4373                         && mInputContentType.onEditorActionListener != null
4374                         && mInputContentType.enterDown) {
4375                     mInputContentType.enterDown = false;
4376                     if (mInputContentType.onEditorActionListener.onEditorAction(
4377                             this, EditorInfo.IME_NULL, event)) {
4378                         return true;
4379                     }
4380                 }
4381 
4382                 if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0
4383                         || shouldAdvanceFocusOnEnter()) {
4384                     /*
4385                      * If there is a click listener, just call through to
4386                      * super, which will invoke it.
4387                      *
4388                      * If there isn't a click listener, try to advance focus,
4389                      * but still call through to super, which will reset the
4390                      * pressed state and longpress state.  (It will also
4391                      * call performClick(), but that won't do anything in
4392                      * this case.)
4393                      */
4394                     if (mOnClickListener == null) {
4395                         View v = focusSearch(FOCUS_DOWN);
4396 
4397                         if (v != null) {
4398                             if (!v.requestFocus(FOCUS_DOWN)) {
4399                                 throw new IllegalStateException("focus search returned a view " +
4400                                         "that wasn't able to take focus!");
4401                             }
4402 
4403                             /*
4404                              * Return true because we handled the key; super
4405                              * will return false because there was no click
4406                              * listener.
4407                              */
4408                             super.onKeyUp(keyCode, event);
4409                             return true;
4410                         } else if ((event.getFlags()
4411                                 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
4412                             // No target for next focus, but make sure the IME
4413                             // if this came from it.
4414                             InputMethodManager imm = InputMethodManager.peekInstance();
4415                             if (imm != null) {
4416                                 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4417                             }
4418                         }
4419                     }
4420 
4421                     return super.onKeyUp(keyCode, event);
4422                 }
4423         }
4424 
4425         if (mInput != null)
4426             if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
4427                 return true;
4428 
4429         if (mMovement != null && mLayout != null)
4430             if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
4431                 return true;
4432 
4433         return super.onKeyUp(keyCode, event);
4434     }
4435 
onCheckIsTextEditor()4436     @Override public boolean onCheckIsTextEditor() {
4437         return mInputType != EditorInfo.TYPE_NULL;
4438     }
4439 
onCreateInputConnection(EditorInfo outAttrs)4440     @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4441         if (onCheckIsTextEditor()) {
4442             if (mInputMethodState == null) {
4443                 mInputMethodState = new InputMethodState();
4444             }
4445             outAttrs.inputType = mInputType;
4446             if (mInputContentType != null) {
4447                 outAttrs.imeOptions = mInputContentType.imeOptions;
4448                 outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
4449                 outAttrs.actionLabel = mInputContentType.imeActionLabel;
4450                 outAttrs.actionId = mInputContentType.imeActionId;
4451                 outAttrs.extras = mInputContentType.extras;
4452             } else {
4453                 outAttrs.imeOptions = EditorInfo.IME_NULL;
4454             }
4455             if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
4456                     == EditorInfo.IME_ACTION_UNSPECIFIED) {
4457                 if (focusSearch(FOCUS_DOWN) != null) {
4458                     // An action has not been set, but the enter key will move to
4459                     // the next focus, so set the action to that.
4460                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
4461                 } else {
4462                     // An action has not been set, and there is no focus to move
4463                     // to, so let's just supply a "done" action.
4464                     outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
4465                 }
4466                 if (!shouldAdvanceFocusOnEnter()) {
4467                     outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4468                 }
4469             }
4470             if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS
4471                     | InputType.TYPE_TEXT_FLAG_MULTI_LINE))
4472                     == (InputType.TYPE_CLASS_TEXT
4473                             | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) {
4474                 // Multi-line text editors should always show an enter key.
4475                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
4476             }
4477             outAttrs.hintText = mHint;
4478             if (mText instanceof Editable) {
4479                 InputConnection ic = new EditableInputConnection(this);
4480                 outAttrs.initialSelStart = Selection.getSelectionStart(mText);
4481                 outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
4482                 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
4483                 return ic;
4484             }
4485         }
4486         return null;
4487     }
4488 
4489     /**
4490      * If this TextView contains editable content, extract a portion of it
4491      * based on the information in <var>request</var> in to <var>outText</var>.
4492      * @return Returns true if the text was successfully extracted, else false.
4493      */
extractText(ExtractedTextRequest request, ExtractedText outText)4494     public boolean extractText(ExtractedTextRequest request,
4495             ExtractedText outText) {
4496         return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN,
4497                 EXTRACT_UNKNOWN, outText);
4498     }
4499 
4500     static final int EXTRACT_NOTHING = -2;
4501     static final int EXTRACT_UNKNOWN = -1;
4502 
extractTextInternal(ExtractedTextRequest request, int partialStartOffset, int partialEndOffset, int delta, ExtractedText outText)4503     boolean extractTextInternal(ExtractedTextRequest request,
4504             int partialStartOffset, int partialEndOffset, int delta,
4505             ExtractedText outText) {
4506         final CharSequence content = mText;
4507         if (content != null) {
4508             if (partialStartOffset != EXTRACT_NOTHING) {
4509                 final int N = content.length();
4510                 if (partialStartOffset < 0) {
4511                     outText.partialStartOffset = outText.partialEndOffset = -1;
4512                     partialStartOffset = 0;
4513                     partialEndOffset = N;
4514                 } else {
4515                     // Adjust offsets to ensure we contain full spans.
4516                     if (content instanceof Spanned) {
4517                         Spanned spanned = (Spanned)content;
4518                         Object[] spans = spanned.getSpans(partialStartOffset,
4519                                 partialEndOffset, ParcelableSpan.class);
4520                         int i = spans.length;
4521                         while (i > 0) {
4522                             i--;
4523                             int j = spanned.getSpanStart(spans[i]);
4524                             if (j < partialStartOffset) partialStartOffset = j;
4525                             j = spanned.getSpanEnd(spans[i]);
4526                             if (j > partialEndOffset) partialEndOffset = j;
4527                         }
4528                     }
4529                     outText.partialStartOffset = partialStartOffset;
4530                     outText.partialEndOffset = partialEndOffset;
4531                     // Now use the delta to determine the actual amount of text
4532                     // we need.
4533                     partialEndOffset += delta;
4534                     if (partialEndOffset > N) {
4535                         partialEndOffset = N;
4536                     } else if (partialEndOffset < 0) {
4537                         partialEndOffset = 0;
4538                     }
4539                 }
4540                 if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) {
4541                     outText.text = content.subSequence(partialStartOffset,
4542                             partialEndOffset);
4543                 } else {
4544                     outText.text = TextUtils.substring(content, partialStartOffset,
4545                             partialEndOffset);
4546                 }
4547             }
4548             outText.flags = 0;
4549             if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
4550                 outText.flags |= ExtractedText.FLAG_SELECTING;
4551             }
4552             if (mSingleLine) {
4553                 outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
4554             }
4555             outText.startOffset = 0;
4556             outText.selectionStart = Selection.getSelectionStart(content);
4557             outText.selectionEnd = Selection.getSelectionEnd(content);
4558             return true;
4559         }
4560         return false;
4561     }
4562 
reportExtractedText()4563     boolean reportExtractedText() {
4564         final InputMethodState ims = mInputMethodState;
4565         if (ims != null) {
4566             final boolean contentChanged = ims.mContentChanged;
4567             if (contentChanged || ims.mSelectionModeChanged) {
4568                 ims.mContentChanged = false;
4569                 ims.mSelectionModeChanged = false;
4570                 final ExtractedTextRequest req = mInputMethodState.mExtracting;
4571                 if (req != null) {
4572                     InputMethodManager imm = InputMethodManager.peekInstance();
4573                     if (imm != null) {
4574                         if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
4575                                 + ims.mChangedStart + " end=" + ims.mChangedEnd
4576                                 + " delta=" + ims.mChangedDelta);
4577                         if (ims.mChangedStart < 0 && !contentChanged) {
4578                             ims.mChangedStart = EXTRACT_NOTHING;
4579                         }
4580                         if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
4581                                 ims.mChangedDelta, ims.mTmpExtracted)) {
4582                             if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
4583                                     + ims.mTmpExtracted.partialStartOffset
4584                                     + " end=" + ims.mTmpExtracted.partialEndOffset
4585                                     + ": " + ims.mTmpExtracted.text);
4586                             imm.updateExtractedText(this, req.token,
4587                                     mInputMethodState.mTmpExtracted);
4588                             return true;
4589                         }
4590                     }
4591                 }
4592             }
4593         }
4594         return false;
4595     }
4596 
4597     /**
4598      * This is used to remove all style-impacting spans from text before new
4599      * extracted text is being replaced into it, so that we don't have any
4600      * lingering spans applied during the replace.
4601      */
removeParcelableSpans(Spannable spannable, int start, int end)4602     static void removeParcelableSpans(Spannable spannable, int start, int end) {
4603         Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
4604         int i = spans.length;
4605         while (i > 0) {
4606             i--;
4607             spannable.removeSpan(spans[i]);
4608         }
4609     }
4610 
4611     /**
4612      * Apply to this text view the given extracted text, as previously
4613      * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
4614      */
setExtractedText(ExtractedText text)4615     public void setExtractedText(ExtractedText text) {
4616         Editable content = getEditableText();
4617         if (text.text != null) {
4618             if (content == null) {
4619                 setText(text.text, TextView.BufferType.EDITABLE);
4620             } else if (text.partialStartOffset < 0) {
4621                 removeParcelableSpans(content, 0, content.length());
4622                 content.replace(0, content.length(), text.text);
4623             } else {
4624                 final int N = content.length();
4625                 int start = text.partialStartOffset;
4626                 if (start > N) start = N;
4627                 int end = text.partialEndOffset;
4628                 if (end > N) end = N;
4629                 removeParcelableSpans(content, start, end);
4630                 content.replace(start, end, text.text);
4631             }
4632         }
4633 
4634         // Now set the selection position...  make sure it is in range, to
4635         // avoid crashes.  If this is a partial update, it is possible that
4636         // the underlying text may have changed, causing us problems here.
4637         // Also we just don't want to trust clients to do the right thing.
4638         Spannable sp = (Spannable)getText();
4639         final int N = sp.length();
4640         int start = text.selectionStart;
4641         if (start < 0) start = 0;
4642         else if (start > N) start = N;
4643         int end = text.selectionEnd;
4644         if (end < 0) end = 0;
4645         else if (end > N) end = N;
4646         Selection.setSelection(sp, start, end);
4647 
4648         // Finally, update the selection mode.
4649         if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
4650             MetaKeyKeyListener.startSelecting(this, sp);
4651         } else {
4652             MetaKeyKeyListener.stopSelecting(this, sp);
4653         }
4654     }
4655 
4656     /**
4657      * @hide
4658      */
setExtracting(ExtractedTextRequest req)4659     public void setExtracting(ExtractedTextRequest req) {
4660         if (mInputMethodState != null) {
4661             mInputMethodState.mExtracting = req;
4662         }
4663     }
4664 
4665     /**
4666      * Called by the framework in response to a text completion from
4667      * the current input method, provided by it calling
4668      * {@link InputConnection#commitCompletion
4669      * InputConnection.commitCompletion()}.  The default implementation does
4670      * nothing; text views that are supporting auto-completion should override
4671      * this to do their desired behavior.
4672      *
4673      * @param text The auto complete text the user has selected.
4674      */
onCommitCompletion(CompletionInfo text)4675     public void onCommitCompletion(CompletionInfo text) {
4676     }
4677 
beginBatchEdit()4678     public void beginBatchEdit() {
4679         final InputMethodState ims = mInputMethodState;
4680         if (ims != null) {
4681             int nesting = ++ims.mBatchEditNesting;
4682             if (nesting == 1) {
4683                 ims.mCursorChanged = false;
4684                 ims.mChangedDelta = 0;
4685                 if (ims.mContentChanged) {
4686                     // We already have a pending change from somewhere else,
4687                     // so turn this into a full update.
4688                     ims.mChangedStart = 0;
4689                     ims.mChangedEnd = mText.length();
4690                 } else {
4691                     ims.mChangedStart = EXTRACT_UNKNOWN;
4692                     ims.mChangedEnd = EXTRACT_UNKNOWN;
4693                     ims.mContentChanged = false;
4694                 }
4695                 onBeginBatchEdit();
4696             }
4697         }
4698     }
4699 
endBatchEdit()4700     public void endBatchEdit() {
4701         final InputMethodState ims = mInputMethodState;
4702         if (ims != null) {
4703             int nesting = --ims.mBatchEditNesting;
4704             if (nesting == 0) {
4705                 finishBatchEdit(ims);
4706             }
4707         }
4708     }
4709 
ensureEndedBatchEdit()4710     void ensureEndedBatchEdit() {
4711         final InputMethodState ims = mInputMethodState;
4712         if (ims != null && ims.mBatchEditNesting != 0) {
4713             ims.mBatchEditNesting = 0;
4714             finishBatchEdit(ims);
4715         }
4716     }
4717 
finishBatchEdit(final InputMethodState ims)4718     void finishBatchEdit(final InputMethodState ims) {
4719         onEndBatchEdit();
4720 
4721         if (ims.mContentChanged || ims.mSelectionModeChanged) {
4722             updateAfterEdit();
4723             reportExtractedText();
4724         } else if (ims.mCursorChanged) {
4725             // Cheezy way to get us to report the current cursor location.
4726             invalidateCursor();
4727         }
4728     }
4729 
updateAfterEdit()4730     void updateAfterEdit() {
4731         invalidate();
4732         int curs = Selection.getSelectionStart(mText);
4733 
4734         if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
4735                              Gravity.BOTTOM) {
4736             registerForPreDraw();
4737         }
4738 
4739         if (curs >= 0) {
4740             mHighlightPathBogus = true;
4741 
4742             if (isFocused()) {
4743                 mShowCursor = SystemClock.uptimeMillis();
4744                 makeBlink();
4745             }
4746         }
4747 
4748         checkForResize();
4749     }
4750 
4751     /**
4752      * Called by the framework in response to a request to begin a batch
4753      * of edit operations through a call to link {@link #beginBatchEdit()}.
4754      */
onBeginBatchEdit()4755     public void onBeginBatchEdit() {
4756     }
4757 
4758     /**
4759      * Called by the framework in response to a request to end a batch
4760      * of edit operations through a call to link {@link #endBatchEdit}.
4761      */
onEndBatchEdit()4762     public void onEndBatchEdit() {
4763     }
4764 
4765     /**
4766      * Called by the framework in response to a private command from the
4767      * current method, provided by it calling
4768      * {@link InputConnection#performPrivateCommand
4769      * InputConnection.performPrivateCommand()}.
4770      *
4771      * @param action The action name of the command.
4772      * @param data Any additional data for the command.  This may be null.
4773      * @return Return true if you handled the command, else false.
4774      */
onPrivateIMECommand(String action, Bundle data)4775     public boolean onPrivateIMECommand(String action, Bundle data) {
4776         return false;
4777     }
4778 
nullLayouts()4779     private void nullLayouts() {
4780         if (mLayout instanceof BoringLayout && mSavedLayout == null) {
4781             mSavedLayout = (BoringLayout) mLayout;
4782         }
4783         if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
4784             mSavedHintLayout = (BoringLayout) mHintLayout;
4785         }
4786 
4787         mLayout = mHintLayout = null;
4788     }
4789 
4790     /**
4791      * Make a new Layout based on the already-measured size of the view,
4792      * on the assumption that it was measured correctly at some point.
4793      */
assumeLayout()4794     private void assumeLayout() {
4795         int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
4796 
4797         if (width < 1) {
4798             width = 0;
4799         }
4800 
4801         int physicalWidth = width;
4802 
4803         if (mHorizontallyScrolling) {
4804             width = VERY_WIDE;
4805         }
4806 
4807         makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
4808                       physicalWidth, false);
4809     }
4810 
4811     /**
4812      * The width passed in is now the desired layout width,
4813      * not the full view width with padding.
4814      * {@hide}
4815      */
makeNewLayout(int w, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)4816     protected void makeNewLayout(int w, int hintWidth,
4817                                  BoringLayout.Metrics boring,
4818                                  BoringLayout.Metrics hintBoring,
4819                                  int ellipsisWidth, boolean bringIntoView) {
4820         stopMarquee();
4821 
4822         mHighlightPathBogus = true;
4823 
4824         if (w < 0) {
4825             w = 0;
4826         }
4827         if (hintWidth < 0) {
4828             hintWidth = 0;
4829         }
4830 
4831         Layout.Alignment alignment;
4832         switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
4833             case Gravity.CENTER_HORIZONTAL:
4834                 alignment = Layout.Alignment.ALIGN_CENTER;
4835                 break;
4836 
4837             case Gravity.RIGHT:
4838                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
4839                 break;
4840 
4841             default:
4842                 alignment = Layout.Alignment.ALIGN_NORMAL;
4843         }
4844 
4845         boolean shouldEllipsize = mEllipsize != null && mInput == null;
4846 
4847         if (mText instanceof Spannable) {
4848             mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
4849                     alignment, mSpacingMult,
4850                     mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
4851                     ellipsisWidth);
4852         } else {
4853             if (boring == UNKNOWN_BORING) {
4854                 boring = BoringLayout.isBoring(mTransformed, mTextPaint,
4855                                                mBoring);
4856                 if (boring != null) {
4857                     mBoring = boring;
4858                 }
4859             }
4860 
4861             if (boring != null) {
4862                 if (boring.width <= w &&
4863                     (mEllipsize == null || boring.width <= ellipsisWidth)) {
4864                     if (mSavedLayout != null) {
4865                         mLayout = mSavedLayout.
4866                                 replaceOrMake(mTransformed, mTextPaint,
4867                                 w, alignment, mSpacingMult, mSpacingAdd,
4868                                 boring, mIncludePad);
4869                     } else {
4870                         mLayout = BoringLayout.make(mTransformed, mTextPaint,
4871                                 w, alignment, mSpacingMult, mSpacingAdd,
4872                                 boring, mIncludePad);
4873                     }
4874                     // Log.e("aaa", "Boring: " + mTransformed);
4875 
4876                     mSavedLayout = (BoringLayout) mLayout;
4877                 } else if (shouldEllipsize && boring.width <= w) {
4878                     if (mSavedLayout != null) {
4879                         mLayout = mSavedLayout.
4880                                 replaceOrMake(mTransformed, mTextPaint,
4881                                 w, alignment, mSpacingMult, mSpacingAdd,
4882                                 boring, mIncludePad, mEllipsize,
4883                                 ellipsisWidth);
4884                     } else {
4885                         mLayout = BoringLayout.make(mTransformed, mTextPaint,
4886                                 w, alignment, mSpacingMult, mSpacingAdd,
4887                                 boring, mIncludePad, mEllipsize,
4888                                 ellipsisWidth);
4889                     }
4890                 } else if (shouldEllipsize) {
4891                     mLayout = new StaticLayout(mTransformed,
4892                                 0, mTransformed.length(),
4893                                 mTextPaint, w, alignment, mSpacingMult,
4894                                 mSpacingAdd, mIncludePad, mEllipsize,
4895                                 ellipsisWidth);
4896                 } else {
4897                     mLayout = new StaticLayout(mTransformed, mTextPaint,
4898                             w, alignment, mSpacingMult, mSpacingAdd,
4899                             mIncludePad);
4900                     // Log.e("aaa", "Boring but wide: " + mTransformed);
4901                 }
4902             } else if (shouldEllipsize) {
4903                 mLayout = new StaticLayout(mTransformed,
4904                             0, mTransformed.length(),
4905                             mTextPaint, w, alignment, mSpacingMult,
4906                             mSpacingAdd, mIncludePad, mEllipsize,
4907                             ellipsisWidth);
4908             } else {
4909                 mLayout = new StaticLayout(mTransformed, mTextPaint,
4910                         w, alignment, mSpacingMult, mSpacingAdd,
4911                         mIncludePad);
4912             }
4913         }
4914 
4915         shouldEllipsize = mEllipsize != null;
4916         mHintLayout = null;
4917 
4918         if (mHint != null) {
4919             if (shouldEllipsize) hintWidth = w;
4920 
4921             if (hintBoring == UNKNOWN_BORING) {
4922                 hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
4923                                                    mHintBoring);
4924                 if (hintBoring != null) {
4925                     mHintBoring = hintBoring;
4926                 }
4927             }
4928 
4929             if (hintBoring != null) {
4930                 if (hintBoring.width <= hintWidth &&
4931                     (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
4932                     if (mSavedHintLayout != null) {
4933                         mHintLayout = mSavedHintLayout.
4934                                 replaceOrMake(mHint, mTextPaint,
4935                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
4936                                 hintBoring, mIncludePad);
4937                     } else {
4938                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
4939                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
4940                                 hintBoring, mIncludePad);
4941                     }
4942 
4943                     mSavedHintLayout = (BoringLayout) mHintLayout;
4944                 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
4945                     if (mSavedHintLayout != null) {
4946                         mHintLayout = mSavedHintLayout.
4947                                 replaceOrMake(mHint, mTextPaint,
4948                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
4949                                 hintBoring, mIncludePad, mEllipsize,
4950                                 ellipsisWidth);
4951                     } else {
4952                         mHintLayout = BoringLayout.make(mHint, mTextPaint,
4953                                 hintWidth, alignment, mSpacingMult, mSpacingAdd,
4954                                 hintBoring, mIncludePad, mEllipsize,
4955                                 ellipsisWidth);
4956                     }
4957                 } else if (shouldEllipsize) {
4958                     mHintLayout = new StaticLayout(mHint,
4959                                 0, mHint.length(),
4960                                 mTextPaint, hintWidth, alignment, mSpacingMult,
4961                                 mSpacingAdd, mIncludePad, mEllipsize,
4962                                 ellipsisWidth);
4963                 } else {
4964                     mHintLayout = new StaticLayout(mHint, mTextPaint,
4965                             hintWidth, alignment, mSpacingMult, mSpacingAdd,
4966                             mIncludePad);
4967                 }
4968             } else if (shouldEllipsize) {
4969                 mHintLayout = new StaticLayout(mHint,
4970                             0, mHint.length(),
4971                             mTextPaint, hintWidth, alignment, mSpacingMult,
4972                             mSpacingAdd, mIncludePad, mEllipsize,
4973                             ellipsisWidth);
4974             } else {
4975                 mHintLayout = new StaticLayout(mHint, mTextPaint,
4976                         hintWidth, alignment, mSpacingMult, mSpacingAdd,
4977                         mIncludePad);
4978             }
4979         }
4980 
4981         if (bringIntoView) {
4982             registerForPreDraw();
4983         }
4984 
4985         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4986             if (!compressText(ellipsisWidth)) {
4987                 final int height = mLayoutParams.height;
4988                 // If the size of the view does not depend on the size of the text, try to
4989                 // start the marquee immediately
4990                 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
4991                     startMarquee();
4992                 } else {
4993                     // Defer the start of the marquee until we know our width (see setFrame())
4994                     mRestartMarquee = true;
4995                 }
4996             }
4997         }
4998     }
4999 
compressText(float width)5000     private boolean compressText(float width) {
5001         // Only compress the text if it hasn't been compressed by the previous pass
5002         if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
5003                 mTextPaint.getTextScaleX() == 1.0f) {
5004             final float textWidth = mLayout.getLineWidth(0);
5005             final float overflow = (textWidth + 1.0f - width) / width;
5006             if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
5007                 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
5008                 post(new Runnable() {
5009                     public void run() {
5010                         requestLayout();
5011                     }
5012                 });
5013                 return true;
5014             }
5015         }
5016 
5017         return false;
5018     }
5019 
desired(Layout layout)5020     private static int desired(Layout layout) {
5021         int n = layout.getLineCount();
5022         CharSequence text = layout.getText();
5023         float max = 0;
5024 
5025         // if any line was wrapped, we can't use it.
5026         // but it's ok for the last line not to have a newline
5027 
5028         for (int i = 0; i < n - 1; i++) {
5029             if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
5030                 return -1;
5031         }
5032 
5033         for (int i = 0; i < n; i++) {
5034             max = Math.max(max, layout.getLineWidth(i));
5035         }
5036 
5037         return (int) FloatMath.ceil(max);
5038     }
5039 
5040     /**
5041      * Set whether the TextView includes extra top and bottom padding to make
5042      * room for accents that go above the normal ascent and descent.
5043      * The default is true.
5044      *
5045      * @attr ref android.R.styleable#TextView_includeFontPadding
5046      */
setIncludeFontPadding(boolean includepad)5047     public void setIncludeFontPadding(boolean includepad) {
5048         mIncludePad = includepad;
5049 
5050         if (mLayout != null) {
5051             nullLayouts();
5052             requestLayout();
5053             invalidate();
5054         }
5055     }
5056 
5057     private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
5058 
5059     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)5060     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
5061         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
5062         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
5063         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
5064         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
5065 
5066         int width;
5067         int height;
5068 
5069         BoringLayout.Metrics boring = UNKNOWN_BORING;
5070         BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
5071 
5072         int des = -1;
5073         boolean fromexisting = false;
5074 
5075         if (widthMode == MeasureSpec.EXACTLY) {
5076             // Parent has told us how big to be. So be it.
5077             width = widthSize;
5078         } else {
5079             if (mLayout != null && mEllipsize == null) {
5080                 des = desired(mLayout);
5081             }
5082 
5083             if (des < 0) {
5084                 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
5085                 if (boring != null) {
5086                     mBoring = boring;
5087                 }
5088             } else {
5089                 fromexisting = true;
5090             }
5091 
5092             if (boring == null || boring == UNKNOWN_BORING) {
5093                 if (des < 0) {
5094                     des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
5095                 }
5096 
5097                 width = des;
5098             } else {
5099                 width = boring.width;
5100             }
5101 
5102             final Drawables dr = mDrawables;
5103             if (dr != null) {
5104                 width = Math.max(width, dr.mDrawableWidthTop);
5105                 width = Math.max(width, dr.mDrawableWidthBottom);
5106             }
5107 
5108             if (mHint != null) {
5109                 int hintDes = -1;
5110                 int hintWidth;
5111 
5112                 if (mHintLayout != null && mEllipsize == null) {
5113                     hintDes = desired(mHintLayout);
5114                 }
5115 
5116                 if (hintDes < 0) {
5117                     hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
5118                     if (hintBoring != null) {
5119                         mHintBoring = hintBoring;
5120                     }
5121                 }
5122 
5123                 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
5124                     if (hintDes < 0) {
5125                         hintDes = (int) FloatMath.ceil(
5126                                 Layout.getDesiredWidth(mHint, mTextPaint));
5127                     }
5128 
5129                     hintWidth = hintDes;
5130                 } else {
5131                     hintWidth = hintBoring.width;
5132                 }
5133 
5134                 if (hintWidth > width) {
5135                     width = hintWidth;
5136                 }
5137             }
5138 
5139             width += getCompoundPaddingLeft() + getCompoundPaddingRight();
5140 
5141             if (mMaxWidthMode == EMS) {
5142                 width = Math.min(width, mMaxWidth * getLineHeight());
5143             } else {
5144                 width = Math.min(width, mMaxWidth);
5145             }
5146 
5147             if (mMinWidthMode == EMS) {
5148                 width = Math.max(width, mMinWidth * getLineHeight());
5149             } else {
5150                 width = Math.max(width, mMinWidth);
5151             }
5152 
5153             // Check against our minimum width
5154             width = Math.max(width, getSuggestedMinimumWidth());
5155 
5156             if (widthMode == MeasureSpec.AT_MOST) {
5157                 width = Math.min(widthSize, width);
5158             }
5159         }
5160 
5161         int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
5162         int unpaddedWidth = want;
5163         int hintWant = want;
5164 
5165         if (mHorizontallyScrolling)
5166             want = VERY_WIDE;
5167 
5168         int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth();
5169 
5170         if (mLayout == null) {
5171             makeNewLayout(want, hintWant, boring, hintBoring,
5172                           width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5173         } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
5174                    (mLayout.getEllipsizedWidth() !=
5175                         width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5176             if (mHint == null && mEllipsize == null &&
5177                     want > mLayout.getWidth() &&
5178                     (mLayout instanceof BoringLayout ||
5179                             (fromexisting && des >= 0 && des <= want))) {
5180                 mLayout.increaseWidthTo(want);
5181             } else {
5182                 makeNewLayout(want, hintWant, boring, hintBoring,
5183                               width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
5184             }
5185         } else {
5186             // Width has not changed.
5187         }
5188 
5189         if (heightMode == MeasureSpec.EXACTLY) {
5190             // Parent has told us how big to be. So be it.
5191             height = heightSize;
5192             mDesiredHeightAtMeasure = -1;
5193         } else {
5194             int desired = getDesiredHeight();
5195 
5196             height = desired;
5197             mDesiredHeightAtMeasure = desired;
5198 
5199             if (heightMode == MeasureSpec.AT_MOST) {
5200                 height = Math.min(desired, height);
5201             }
5202         }
5203 
5204         int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
5205         if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
5206             unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
5207         }
5208 
5209         /*
5210          * We didn't let makeNewLayout() register to bring the cursor into view,
5211          * so do it here if there is any possibility that it is needed.
5212          */
5213         if (mMovement != null ||
5214             mLayout.getWidth() > unpaddedWidth ||
5215             mLayout.getHeight() > unpaddedHeight) {
5216             registerForPreDraw();
5217         } else {
5218             scrollTo(0, 0);
5219         }
5220 
5221         setMeasuredDimension(width, height);
5222     }
5223 
getDesiredHeight()5224     private int getDesiredHeight() {
5225         return Math.max(
5226                 getDesiredHeight(mLayout, true),
5227                 getDesiredHeight(mHintLayout, mEllipsize != null));
5228     }
5229 
getDesiredHeight(Layout layout, boolean cap)5230     private int getDesiredHeight(Layout layout, boolean cap) {
5231         if (layout == null) {
5232             return 0;
5233         }
5234 
5235         int linecount = layout.getLineCount();
5236         int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
5237         int desired = layout.getLineTop(linecount);
5238 
5239         final Drawables dr = mDrawables;
5240         if (dr != null) {
5241             desired = Math.max(desired, dr.mDrawableHeightLeft);
5242             desired = Math.max(desired, dr.mDrawableHeightRight);
5243         }
5244 
5245         desired += pad;
5246 
5247         if (mMaxMode == LINES) {
5248             /*
5249              * Don't cap the hint to a certain number of lines.
5250              * (Do cap it, though, if we have a maximum pixel height.)
5251              */
5252             if (cap) {
5253                 if (linecount > mMaximum) {
5254                     desired = layout.getLineTop(mMaximum) +
5255                               layout.getBottomPadding();
5256 
5257                     if (dr != null) {
5258                         desired = Math.max(desired, dr.mDrawableHeightLeft);
5259                         desired = Math.max(desired, dr.mDrawableHeightRight);
5260                     }
5261 
5262                     desired += pad;
5263                     linecount = mMaximum;
5264                 }
5265             }
5266         } else {
5267             desired = Math.min(desired, mMaximum);
5268         }
5269 
5270         if (mMinMode == LINES) {
5271             if (linecount < mMinimum) {
5272                 desired += getLineHeight() * (mMinimum - linecount);
5273             }
5274         } else {
5275             desired = Math.max(desired, mMinimum);
5276         }
5277 
5278         // Check against our minimum height
5279         desired = Math.max(desired, getSuggestedMinimumHeight());
5280 
5281         return desired;
5282     }
5283 
5284     /**
5285      * Check whether a change to the existing text layout requires a
5286      * new view layout.
5287      */
checkForResize()5288     private void checkForResize() {
5289         boolean sizeChanged = false;
5290 
5291         if (mLayout != null) {
5292             // Check if our width changed
5293             if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
5294                 sizeChanged = true;
5295                 invalidate();
5296             }
5297 
5298             // Check if our height changed
5299             if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
5300                 int desiredHeight = getDesiredHeight();
5301 
5302                 if (desiredHeight != this.getHeight()) {
5303                     sizeChanged = true;
5304                 }
5305             } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
5306                 if (mDesiredHeightAtMeasure >= 0) {
5307                     int desiredHeight = getDesiredHeight();
5308 
5309                     if (desiredHeight != mDesiredHeightAtMeasure) {
5310                         sizeChanged = true;
5311                     }
5312                 }
5313             }
5314         }
5315 
5316         if (sizeChanged) {
5317             requestLayout();
5318             // caller will have already invalidated
5319         }
5320     }
5321 
5322     /**
5323      * Check whether entirely new text requires a new view layout
5324      * or merely a new text layout.
5325      */
checkForRelayout()5326     private void checkForRelayout() {
5327         // If we have a fixed width, we can just swap in a new text layout
5328         // if the text height stays the same or if the view height is fixed.
5329 
5330         if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
5331                 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
5332                 (mHint == null || mHintLayout != null) &&
5333                 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
5334             // Static width, so try making a new text layout.
5335 
5336             int oldht = mLayout.getHeight();
5337             int want = mLayout.getWidth();
5338             int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
5339 
5340             /*
5341              * No need to bring the text into view, since the size is not
5342              * changing (unless we do the requestLayout(), in which case it
5343              * will happen at measure).
5344              */
5345             makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
5346                           mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
5347                           false);
5348 
5349             if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
5350                 // In a fixed-height view, so use our new text layout.
5351                 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
5352                     mLayoutParams.height != LayoutParams.FILL_PARENT) {
5353                     invalidate();
5354                     return;
5355                 }
5356 
5357                 // Dynamic height, but height has stayed the same,
5358                 // so use our new text layout.
5359                 if (mLayout.getHeight() == oldht &&
5360                     (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
5361                     invalidate();
5362                     return;
5363                 }
5364             }
5365 
5366             // We lose: the height has changed and we have a dynamic height.
5367             // Request a new view layout using our new text layout.
5368             requestLayout();
5369             invalidate();
5370         } else {
5371             // Dynamic width, so we have no choice but to request a new
5372             // view layout with a new text layout.
5373 
5374             nullLayouts();
5375             requestLayout();
5376             invalidate();
5377         }
5378     }
5379 
5380     /**
5381      * Returns true if anything changed.
5382      */
bringTextIntoView()5383     private boolean bringTextIntoView() {
5384         int line = 0;
5385         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5386             line = mLayout.getLineCount() - 1;
5387         }
5388 
5389         Layout.Alignment a = mLayout.getParagraphAlignment(line);
5390         int dir = mLayout.getParagraphDirection(line);
5391         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5392         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5393         int ht = mLayout.getHeight();
5394 
5395         int scrollx, scrolly;
5396 
5397         if (a == Layout.Alignment.ALIGN_CENTER) {
5398             /*
5399              * Keep centered if possible, or, if it is too wide to fit,
5400              * keep leading edge in view.
5401              */
5402 
5403             int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5404             int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5405 
5406             if (right - left < hspace) {
5407                 scrollx = (right + left) / 2 - hspace / 2;
5408             } else {
5409                 if (dir < 0) {
5410                     scrollx = right - hspace;
5411                 } else {
5412                     scrollx = left;
5413                 }
5414             }
5415         } else if (a == Layout.Alignment.ALIGN_NORMAL) {
5416             /*
5417              * Keep leading edge in view.
5418              */
5419 
5420             if (dir < 0) {
5421                 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5422                 scrollx = right - hspace;
5423             } else {
5424                 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5425             }
5426         } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ {
5427             /*
5428              * Keep trailing edge in view.
5429              */
5430 
5431             if (dir < 0) {
5432                 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
5433             } else {
5434                 int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5435                 scrollx = right - hspace;
5436             }
5437         }
5438 
5439         if (ht < vspace) {
5440             scrolly = 0;
5441         } else {
5442             if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5443                 scrolly = ht - vspace;
5444             } else {
5445                 scrolly = 0;
5446             }
5447         }
5448 
5449         if (scrollx != mScrollX || scrolly != mScrollY) {
5450             scrollTo(scrollx, scrolly);
5451             return true;
5452         } else {
5453             return false;
5454         }
5455     }
5456 
5457     /**
5458      * Move the point, specified by the offset, into the view if it is needed.
5459      * This has to be called after layout. Returns true if anything changed.
5460      */
bringPointIntoView(int offset)5461     public boolean bringPointIntoView(int offset) {
5462         boolean changed = false;
5463 
5464         int line = mLayout.getLineForOffset(offset);
5465 
5466         // FIXME: Is it okay to truncate this, or should we round?
5467         final int x = (int)mLayout.getPrimaryHorizontal(offset);
5468         final int top = mLayout.getLineTop(line);
5469         final int bottom = mLayout.getLineTop(line+1);
5470 
5471         int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
5472         int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
5473         int ht = mLayout.getHeight();
5474 
5475         int grav;
5476 
5477         switch (mLayout.getParagraphAlignment(line)) {
5478             case ALIGN_NORMAL:
5479                 grav = 1;
5480                 break;
5481 
5482             case ALIGN_OPPOSITE:
5483                 grav = -1;
5484                 break;
5485 
5486             default:
5487                 grav = 0;
5488         }
5489 
5490         grav *= mLayout.getParagraphDirection(line);
5491 
5492         int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5493         int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5494 
5495         int hslack = (bottom - top) / 2;
5496         int vslack = hslack;
5497 
5498         if (vslack > vspace / 4)
5499             vslack = vspace / 4;
5500         if (hslack > hspace / 4)
5501             hslack = hspace / 4;
5502 
5503         int hs = mScrollX;
5504         int vs = mScrollY;
5505 
5506         if (top - vs < vslack)
5507             vs = top - vslack;
5508         if (bottom - vs > vspace - vslack)
5509             vs = bottom - (vspace - vslack);
5510         if (ht - vs < vspace)
5511             vs = ht - vspace;
5512         if (0 - vs > 0)
5513             vs = 0;
5514 
5515         if (grav != 0) {
5516             if (x - hs < hslack) {
5517                 hs = x - hslack;
5518             }
5519             if (x - hs > hspace - hslack) {
5520                 hs = x - (hspace - hslack);
5521             }
5522         }
5523 
5524         if (grav < 0) {
5525             if (left - hs > 0)
5526                 hs = left;
5527             if (right - hs < hspace)
5528                 hs = right - hspace;
5529         } else if (grav > 0) {
5530             if (right - hs < hspace)
5531                 hs = right - hspace;
5532             if (left - hs > 0)
5533                 hs = left;
5534         } else /* grav == 0 */ {
5535             if (right - left <= hspace) {
5536                 /*
5537                  * If the entire text fits, center it exactly.
5538                  */
5539                 hs = left - (hspace - (right - left)) / 2;
5540             } else if (x > right - hslack) {
5541                 /*
5542                  * If we are near the right edge, keep the right edge
5543                  * at the edge of the view.
5544                  */
5545                 hs = right - hspace;
5546             } else if (x < left + hslack) {
5547                 /*
5548                  * If we are near the left edge, keep the left edge
5549                  * at the edge of the view.
5550                  */
5551                 hs = left;
5552             } else if (left > hs) {
5553                 /*
5554                  * Is there whitespace visible at the left?  Fix it if so.
5555                  */
5556                 hs = left;
5557             } else if (right < hs + hspace) {
5558                 /*
5559                  * Is there whitespace visible at the right?  Fix it if so.
5560                  */
5561                 hs = right - hspace;
5562             } else {
5563                 /*
5564                  * Otherwise, float as needed.
5565                  */
5566                 if (x - hs < hslack) {
5567                     hs = x - hslack;
5568                 }
5569                 if (x - hs > hspace - hslack) {
5570                     hs = x - (hspace - hslack);
5571                 }
5572             }
5573         }
5574 
5575         if (hs != mScrollX || vs != mScrollY) {
5576             if (mScroller == null) {
5577                 scrollTo(hs, vs);
5578             } else {
5579                 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
5580                 int dx = hs - mScrollX;
5581                 int dy = vs - mScrollY;
5582 
5583                 if (duration > ANIMATED_SCROLL_GAP) {
5584                     mScroller.startScroll(mScrollX, mScrollY, dx, dy);
5585                     awakenScrollBars(mScroller.getDuration());
5586                     invalidate();
5587                 } else {
5588                     if (!mScroller.isFinished()) {
5589                         mScroller.abortAnimation();
5590                     }
5591 
5592                     scrollBy(dx, dy);
5593                 }
5594 
5595                 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
5596             }
5597 
5598             changed = true;
5599         }
5600 
5601         if (isFocused()) {
5602             // This offsets because getInterestingRect() is in terms of
5603             // viewport coordinates, but requestRectangleOnScreen()
5604             // is in terms of content coordinates.
5605 
5606             Rect r = new Rect();
5607             getInterestingRect(r, x, top, bottom, line);
5608             r.offset(mScrollX, mScrollY);
5609 
5610             if (requestRectangleOnScreen(r)) {
5611                 changed = true;
5612             }
5613         }
5614 
5615         return changed;
5616     }
5617 
5618     /**
5619      * Move the cursor, if needed, so that it is at an offset that is visible
5620      * to the user.  This will not move the cursor if it represents more than
5621      * one character (a selection range).  This will only work if the
5622      * TextView contains spannable text; otherwise it will do nothing.
5623      */
moveCursorToVisibleOffset()5624     public boolean moveCursorToVisibleOffset() {
5625         if (!(mText instanceof Spannable)) {
5626             return false;
5627         }
5628         int start = Selection.getSelectionStart(mText);
5629         int end = Selection.getSelectionEnd(mText);
5630         if (start != end) {
5631             return false;
5632         }
5633 
5634         // First: make sure the line is visible on screen:
5635 
5636         int line = mLayout.getLineForOffset(start);
5637 
5638         final int top = mLayout.getLineTop(line);
5639         final int bottom = mLayout.getLineTop(line+1);
5640         final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
5641         int vslack = (bottom - top) / 2;
5642         if (vslack > vspace / 4)
5643             vslack = vspace / 4;
5644         final int vs = mScrollY;
5645 
5646         if (top < (vs+vslack)) {
5647             line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
5648         } else if (bottom > (vspace+vs-vslack)) {
5649             line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
5650         }
5651 
5652         // Next: make sure the character is visible on screen:
5653 
5654         final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5655         final int hs = mScrollX;
5656         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
5657         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
5658 
5659         int newStart = start;
5660         if (newStart < leftChar) {
5661             newStart = leftChar;
5662         } else if (newStart > rightChar) {
5663             newStart = rightChar;
5664         }
5665 
5666         if (newStart != start) {
5667             Selection.setSelection((Spannable)mText, newStart);
5668             return true;
5669         }
5670 
5671         return false;
5672     }
5673 
5674     @Override
computeScroll()5675     public void computeScroll() {
5676         if (mScroller != null) {
5677             if (mScroller.computeScrollOffset()) {
5678                 mScrollX = mScroller.getCurrX();
5679                 mScrollY = mScroller.getCurrY();
5680                 postInvalidate();  // So we draw again
5681             }
5682         }
5683     }
5684 
getInterestingRect(Rect r, int h, int top, int bottom, int line)5685     private void getInterestingRect(Rect r, int h, int top, int bottom,
5686                                     int line) {
5687         int paddingTop = getExtendedPaddingTop();
5688         if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5689             paddingTop += getVerticalOffset(false);
5690         }
5691         top += paddingTop;
5692         bottom += paddingTop;
5693         h += getCompoundPaddingLeft();
5694 
5695         if (line == 0)
5696             top -= getExtendedPaddingTop();
5697         if (line == mLayout.getLineCount() - 1)
5698             bottom += getExtendedPaddingBottom();
5699 
5700         r.set(h, top, h+1, bottom);
5701         r.offset(-mScrollX, -mScrollY);
5702     }
5703 
5704     @Override
debug(int depth)5705     public void debug(int depth) {
5706         super.debug(depth);
5707 
5708         String output = debugIndent(depth);
5709         output += "frame={" + mLeft + ", " + mTop + ", " + mRight
5710                 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
5711                 + "} ";
5712 
5713         if (mText != null) {
5714 
5715             output += "mText=\"" + mText + "\" ";
5716             if (mLayout != null) {
5717                 output += "mLayout width=" + mLayout.getWidth()
5718                         + " height=" + mLayout.getHeight();
5719             }
5720         } else {
5721             output += "mText=NULL";
5722         }
5723         Log.d(VIEW_LOG_TAG, output);
5724     }
5725 
5726     /**
5727      * Convenience for {@link Selection#getSelectionStart}.
5728      */
getSelectionStart()5729     public int getSelectionStart() {
5730         return Selection.getSelectionStart(getText());
5731     }
5732 
5733     /**
5734      * Convenience for {@link Selection#getSelectionEnd}.
5735      */
getSelectionEnd()5736     public int getSelectionEnd() {
5737         return Selection.getSelectionEnd(getText());
5738     }
5739 
5740     /**
5741      * Return true iff there is a selection inside this text view.
5742      */
hasSelection()5743     public boolean hasSelection() {
5744         return getSelectionStart() != getSelectionEnd();
5745     }
5746 
5747     /**
5748      * Sets the properties of this field (lines, horizontally scrolling,
5749      * transformation method) to be for a single-line input.
5750      *
5751      * @attr ref android.R.styleable#TextView_singleLine
5752      */
setSingleLine()5753     public void setSingleLine() {
5754         setSingleLine(true);
5755     }
5756 
5757     /**
5758      * If true, sets the properties of this field (lines, horizontally
5759      * scrolling, transformation method) to be for a single-line input;
5760      * if false, restores these to the default conditions.
5761      * Note that calling this with false restores default conditions,
5762      * not necessarily those that were in effect prior to calling
5763      * it with true.
5764      *
5765      * @attr ref android.R.styleable#TextView_singleLine
5766      */
5767     @android.view.RemotableViewMethod
setSingleLine(boolean singleLine)5768     public void setSingleLine(boolean singleLine) {
5769         if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
5770                 == EditorInfo.TYPE_CLASS_TEXT) {
5771             if (singleLine) {
5772                 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5773             } else {
5774                 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
5775             }
5776         }
5777         applySingleLine(singleLine, true);
5778     }
5779 
applySingleLine(boolean singleLine, boolean applyTransformation)5780     private void applySingleLine(boolean singleLine, boolean applyTransformation) {
5781         mSingleLine = singleLine;
5782         if (singleLine) {
5783             setLines(1);
5784             setHorizontallyScrolling(true);
5785             if (applyTransformation) {
5786                 setTransformationMethod(SingleLineTransformationMethod.
5787                                         getInstance());
5788             }
5789         } else {
5790             setMaxLines(Integer.MAX_VALUE);
5791             setHorizontallyScrolling(false);
5792             if (applyTransformation) {
5793                 setTransformationMethod(null);
5794             }
5795         }
5796     }
5797 
5798     /**
5799      * Causes words in the text that are longer than the view is wide
5800      * to be ellipsized instead of broken in the middle.  You may also
5801      * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
5802      * to constrain the text to a single line.  Use <code>null</code>
5803      * to turn off ellipsizing.
5804      *
5805      * @attr ref android.R.styleable#TextView_ellipsize
5806      */
setEllipsize(TextUtils.TruncateAt where)5807     public void setEllipsize(TextUtils.TruncateAt where) {
5808         mEllipsize = where;
5809 
5810         if (mLayout != null) {
5811             nullLayouts();
5812             requestLayout();
5813             invalidate();
5814         }
5815     }
5816 
5817     /**
5818      * Sets how many times to repeat the marquee animation. Only applied if the
5819      * TextView has marquee enabled. Set to -1 to repeat indefinitely.
5820      *
5821      * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
5822      */
setMarqueeRepeatLimit(int marqueeLimit)5823     public void setMarqueeRepeatLimit(int marqueeLimit) {
5824         mMarqueeRepeatLimit = marqueeLimit;
5825     }
5826 
5827     /**
5828      * Returns where, if anywhere, words that are longer than the view
5829      * is wide should be ellipsized.
5830      */
5831     @ViewDebug.ExportedProperty
getEllipsize()5832     public TextUtils.TruncateAt getEllipsize() {
5833         return mEllipsize;
5834     }
5835 
5836     /**
5837      * Set the TextView so that when it takes focus, all the text is
5838      * selected.
5839      *
5840      * @attr ref android.R.styleable#TextView_selectAllOnFocus
5841      */
5842     @android.view.RemotableViewMethod
setSelectAllOnFocus(boolean selectAllOnFocus)5843     public void setSelectAllOnFocus(boolean selectAllOnFocus) {
5844         mSelectAllOnFocus = selectAllOnFocus;
5845 
5846         if (selectAllOnFocus && !(mText instanceof Spannable)) {
5847             setText(mText, BufferType.SPANNABLE);
5848         }
5849     }
5850 
5851     /**
5852      * Set whether the cursor is visible.  The default is true.
5853      *
5854      * @attr ref android.R.styleable#TextView_cursorVisible
5855      */
5856     @android.view.RemotableViewMethod
setCursorVisible(boolean visible)5857     public void setCursorVisible(boolean visible) {
5858         mCursorVisible = visible;
5859         invalidate();
5860 
5861         if (visible) {
5862             makeBlink();
5863         } else if (mBlink != null) {
5864             mBlink.removeCallbacks(mBlink);
5865         }
5866     }
5867 
canMarquee()5868     private boolean canMarquee() {
5869         int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
5870         return width > 0 && mLayout.getLineWidth(0) > width;
5871     }
5872 
startMarquee()5873     private void startMarquee() {
5874         // Do not ellipsize EditText
5875         if (mInput != null) return;
5876 
5877         if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
5878             return;
5879         }
5880 
5881         if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
5882                 getLineCount() == 1 && canMarquee()) {
5883 
5884             if (mMarquee == null) mMarquee = new Marquee(this);
5885             mMarquee.start(mMarqueeRepeatLimit);
5886         }
5887     }
5888 
stopMarquee()5889     private void stopMarquee() {
5890         if (mMarquee != null && !mMarquee.isStopped()) {
5891             mMarquee.stop();
5892         }
5893     }
5894 
startStopMarquee(boolean start)5895     private void startStopMarquee(boolean start) {
5896         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5897             if (start) {
5898                 startMarquee();
5899             } else {
5900                 stopMarquee();
5901             }
5902         }
5903     }
5904 
5905     private static final class Marquee extends Handler {
5906         // TODO: Add an option to configure this
5907         private static final float MARQUEE_DELTA_MAX = 0.07f;
5908         private static final int MARQUEE_DELAY = 1200;
5909         private static final int MARQUEE_RESTART_DELAY = 1200;
5910         private static final int MARQUEE_RESOLUTION = 1000 / 30;
5911         private static final int MARQUEE_PIXELS_PER_SECOND = 30;
5912 
5913         private static final byte MARQUEE_STOPPED = 0x0;
5914         private static final byte MARQUEE_STARTING = 0x1;
5915         private static final byte MARQUEE_RUNNING = 0x2;
5916 
5917         private static final int MESSAGE_START = 0x1;
5918         private static final int MESSAGE_TICK = 0x2;
5919         private static final int MESSAGE_RESTART = 0x3;
5920 
5921         private final WeakReference<TextView> mView;
5922 
5923         private byte mStatus = MARQUEE_STOPPED;
5924         private float mScrollUnit;
5925         private float mMaxScroll;
5926         float mMaxFadeScroll;
5927         private float mGhostStart;
5928         private float mGhostOffset;
5929         private float mFadeStop;
5930         private int mRepeatLimit;
5931 
5932         float mScroll;
5933 
Marquee(TextView v)5934         Marquee(TextView v) {
5935             final float density = v.getContext().getResources().getDisplayMetrics().density;
5936             mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
5937             mView = new WeakReference<TextView>(v);
5938         }
5939 
5940         @Override
handleMessage(Message msg)5941         public void handleMessage(Message msg) {
5942             switch (msg.what) {
5943                 case MESSAGE_START:
5944                     mStatus = MARQUEE_RUNNING;
5945                     tick();
5946                     break;
5947                 case MESSAGE_TICK:
5948                     tick();
5949                     break;
5950                 case MESSAGE_RESTART:
5951                     if (mStatus == MARQUEE_RUNNING) {
5952                         if (mRepeatLimit >= 0) {
5953                             mRepeatLimit--;
5954                         }
5955                         start(mRepeatLimit);
5956                     }
5957                     break;
5958             }
5959         }
5960 
tick()5961         void tick() {
5962             if (mStatus != MARQUEE_RUNNING) {
5963                 return;
5964             }
5965 
5966             removeMessages(MESSAGE_TICK);
5967 
5968             final TextView textView = mView.get();
5969             if (textView != null && (textView.isFocused() || textView.isSelected())) {
5970                 mScroll += mScrollUnit;
5971                 if (mScroll > mMaxScroll) {
5972                     mScroll = mMaxScroll;
5973                     sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
5974                 } else {
5975                     sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
5976                 }
5977                 textView.invalidate();
5978             }
5979         }
5980 
stop()5981         void stop() {
5982             mStatus = MARQUEE_STOPPED;
5983             removeMessages(MESSAGE_START);
5984             removeMessages(MESSAGE_RESTART);
5985             removeMessages(MESSAGE_TICK);
5986             resetScroll();
5987         }
5988 
resetScroll()5989         private void resetScroll() {
5990             mScroll = 0.0f;
5991             final TextView textView = mView.get();
5992             if (textView != null) textView.invalidate();
5993         }
5994 
start(int repeatLimit)5995         void start(int repeatLimit) {
5996             if (repeatLimit == 0) {
5997                 stop();
5998                 return;
5999             }
6000             mRepeatLimit = repeatLimit;
6001             final TextView textView = mView.get();
6002             if (textView != null && textView.mLayout != null) {
6003                 mStatus = MARQUEE_STARTING;
6004                 mScroll = 0.0f;
6005                 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
6006                         textView.getCompoundPaddingRight();
6007                 final float lineWidth = textView.mLayout.getLineWidth(0);
6008                 final float gap = textWidth / 3.0f;
6009                 mGhostStart = lineWidth - textWidth + gap;
6010                 mMaxScroll = mGhostStart + textWidth;
6011                 mGhostOffset = lineWidth + gap;
6012                 mFadeStop = lineWidth + textWidth / 6.0f;
6013                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
6014 
6015                 textView.invalidate();
6016                 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
6017             }
6018         }
6019 
getGhostOffset()6020         float getGhostOffset() {
6021             return mGhostOffset;
6022         }
6023 
shouldDrawLeftFade()6024         boolean shouldDrawLeftFade() {
6025             return mScroll <= mFadeStop;
6026         }
6027 
shouldDrawGhost()6028         boolean shouldDrawGhost() {
6029             return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
6030         }
6031 
isRunning()6032         boolean isRunning() {
6033             return mStatus == MARQUEE_RUNNING;
6034         }
6035 
isStopped()6036         boolean isStopped() {
6037             return mStatus == MARQUEE_STOPPED;
6038         }
6039     }
6040 
6041     /**
6042      * This method is called when the text is changed, in case any
6043      * subclasses would like to know.
6044      *
6045      * @param text The text the TextView is displaying.
6046      * @param start The offset of the start of the range of the text
6047      *              that was modified.
6048      * @param before The offset of the former end of the range of the
6049      *               text that was modified.  If text was simply inserted,
6050      *               this will be the same as <code>start</code>.
6051      *               If text was replaced with new text or deleted, the
6052      *               length of the old text was <code>before-start</code>.
6053      * @param after The offset of the end of the range of the text
6054      *              that was modified.  If text was simply deleted,
6055      *              this will be the same as <code>start</code>.
6056      *              If text was replaced with new text or inserted,
6057      *              the length of the new text is <code>after-start</code>.
6058      */
onTextChanged(CharSequence text, int start, int before, int after)6059     protected void onTextChanged(CharSequence text,
6060                                  int start, int before, int after) {
6061     }
6062 
6063     /**
6064      * This method is called when the selection has changed, in case any
6065      * subclasses would like to know.
6066      *
6067      * @param selStart The new selection start location.
6068      * @param selEnd The new selection end location.
6069      */
onSelectionChanged(int selStart, int selEnd)6070     protected void onSelectionChanged(int selStart, int selEnd) {
6071     }
6072 
6073     /**
6074      * Adds a TextWatcher to the list of those whose methods are called
6075      * whenever this TextView's text changes.
6076      * <p>
6077      * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
6078      * not called after {@link #setText} calls.  Now, doing {@link #setText}
6079      * if there are any text changed listeners forces the buffer type to
6080      * Editable if it would not otherwise be and does call this method.
6081      */
addTextChangedListener(TextWatcher watcher)6082     public void addTextChangedListener(TextWatcher watcher) {
6083         if (mListeners == null) {
6084             mListeners = new ArrayList<TextWatcher>();
6085         }
6086 
6087         mListeners.add(watcher);
6088     }
6089 
6090     /**
6091      * Removes the specified TextWatcher from the list of those whose
6092      * methods are called
6093      * whenever this TextView's text changes.
6094      */
removeTextChangedListener(TextWatcher watcher)6095     public void removeTextChangedListener(TextWatcher watcher) {
6096         if (mListeners != null) {
6097             int i = mListeners.indexOf(watcher);
6098 
6099             if (i >= 0) {
6100                 mListeners.remove(i);
6101             }
6102         }
6103     }
6104 
sendBeforeTextChanged(CharSequence text, int start, int before, int after)6105     private void sendBeforeTextChanged(CharSequence text, int start, int before,
6106                                    int after) {
6107         if (mListeners != null) {
6108             final ArrayList<TextWatcher> list = mListeners;
6109             final int count = list.size();
6110             for (int i = 0; i < count; i++) {
6111                 list.get(i).beforeTextChanged(text, start, before, after);
6112             }
6113         }
6114     }
6115 
6116     /**
6117      * Not private so it can be called from an inner class without going
6118      * through a thunk.
6119      */
sendOnTextChanged(CharSequence text, int start, int before, int after)6120     void sendOnTextChanged(CharSequence text, int start, int before,
6121                                    int after) {
6122         if (mListeners != null) {
6123             final ArrayList<TextWatcher> list = mListeners;
6124             final int count = list.size();
6125             for (int i = 0; i < count; i++) {
6126                 list.get(i).onTextChanged(text, start, before, after);
6127             }
6128         }
6129     }
6130 
6131     /**
6132      * Not private so it can be called from an inner class without going
6133      * through a thunk.
6134      */
sendAfterTextChanged(Editable text)6135     void sendAfterTextChanged(Editable text) {
6136         if (mListeners != null) {
6137             final ArrayList<TextWatcher> list = mListeners;
6138             final int count = list.size();
6139             for (int i = 0; i < count; i++) {
6140                 list.get(i).afterTextChanged(text);
6141             }
6142         }
6143     }
6144 
6145     /**
6146      * Not private so it can be called from an inner class without going
6147      * through a thunk.
6148      */
handleTextChanged(CharSequence buffer, int start, int before, int after)6149     void handleTextChanged(CharSequence buffer, int start,
6150             int before, int after) {
6151         final InputMethodState ims = mInputMethodState;
6152         if (ims == null || ims.mBatchEditNesting == 0) {
6153             updateAfterEdit();
6154         }
6155         if (ims != null) {
6156             ims.mContentChanged = true;
6157             if (ims.mChangedStart < 0) {
6158                 ims.mChangedStart = start;
6159                 ims.mChangedEnd = start+before;
6160             } else {
6161                 if (ims.mChangedStart > start) ims.mChangedStart = start;
6162                 if (ims.mChangedEnd < (start+before)) ims.mChangedEnd = start+before;
6163             }
6164             ims.mChangedDelta += after-before;
6165         }
6166 
6167         sendOnTextChanged(buffer, start, before, after);
6168         onTextChanged(buffer, start, before, after);
6169     }
6170 
6171     /**
6172      * Not private so it can be called from an inner class without going
6173      * through a thunk.
6174      */
spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)6175     void spanChange(Spanned buf, Object what, int oldStart, int newStart,
6176             int oldEnd, int newEnd) {
6177         // XXX Make the start and end move together if this ends up
6178         // spending too much time invalidating.
6179 
6180         boolean selChanged = false;
6181         int newSelStart=-1, newSelEnd=-1;
6182 
6183         final InputMethodState ims = mInputMethodState;
6184 
6185         if (what == Selection.SELECTION_END) {
6186             mHighlightPathBogus = true;
6187             selChanged = true;
6188             newSelEnd = newStart;
6189 
6190             if (!isFocused()) {
6191                 mSelectionMoved = true;
6192             }
6193 
6194             if (oldStart >= 0 || newStart >= 0) {
6195                 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
6196                 registerForPreDraw();
6197 
6198                 if (isFocused()) {
6199                     mShowCursor = SystemClock.uptimeMillis();
6200                     makeBlink();
6201                 }
6202             }
6203         }
6204 
6205         if (what == Selection.SELECTION_START) {
6206             mHighlightPathBogus = true;
6207             selChanged = true;
6208             newSelStart = newStart;
6209 
6210             if (!isFocused()) {
6211                 mSelectionMoved = true;
6212             }
6213 
6214             if (oldStart >= 0 || newStart >= 0) {
6215                 int end = Selection.getSelectionEnd(buf);
6216                 invalidateCursor(end, oldStart, newStart);
6217             }
6218         }
6219 
6220         if (selChanged) {
6221             if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
6222                 if (newSelStart < 0) {
6223                     newSelStart = Selection.getSelectionStart(buf);
6224                 }
6225                 if (newSelEnd < 0) {
6226                     newSelEnd = Selection.getSelectionEnd(buf);
6227                 }
6228                 onSelectionChanged(newSelStart, newSelEnd);
6229             }
6230         }
6231 
6232         if (what instanceof UpdateAppearance ||
6233             what instanceof ParagraphStyle) {
6234             if (ims == null || ims.mBatchEditNesting == 0) {
6235                 invalidate();
6236                 mHighlightPathBogus = true;
6237                 checkForResize();
6238             } else {
6239                 ims.mContentChanged = true;
6240             }
6241         }
6242 
6243         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
6244             mHighlightPathBogus = true;
6245             if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
6246                 ims.mSelectionModeChanged = true;
6247             }
6248 
6249             if (Selection.getSelectionStart(buf) >= 0) {
6250                 if (ims == null || ims.mBatchEditNesting == 0) {
6251                     invalidateCursor();
6252                 } else {
6253                     ims.mCursorChanged = true;
6254                 }
6255             }
6256         }
6257 
6258         if (what instanceof ParcelableSpan) {
6259             // If this is a span that can be sent to a remote process,
6260             // the current extract editor would be interested in it.
6261             if (ims != null && ims.mExtracting != null) {
6262                 if (ims.mBatchEditNesting != 0) {
6263                     if (oldStart >= 0) {
6264                         if (ims.mChangedStart > oldStart) {
6265                             ims.mChangedStart = oldStart;
6266                         }
6267                         if (ims.mChangedStart > oldEnd) {
6268                             ims.mChangedStart = oldEnd;
6269                         }
6270                     }
6271                     if (newStart >= 0) {
6272                         if (ims.mChangedStart > newStart) {
6273                             ims.mChangedStart = newStart;
6274                         }
6275                         if (ims.mChangedStart > newEnd) {
6276                             ims.mChangedStart = newEnd;
6277                         }
6278                     }
6279                 } else {
6280                     if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
6281                             + oldStart + "-" + oldEnd + ","
6282                             + newStart + "-" + newEnd + what);
6283                     ims.mContentChanged = true;
6284                 }
6285             }
6286         }
6287     }
6288 
6289     private class ChangeWatcher
6290     implements TextWatcher, SpanWatcher {
6291 
6292         private CharSequence mBeforeText;
6293 
beforeTextChanged(CharSequence buffer, int start, int before, int after)6294         public void beforeTextChanged(CharSequence buffer, int start,
6295                                       int before, int after) {
6296             if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
6297                     + " before=" + before + " after=" + after + ": " + buffer);
6298 
6299             if (AccessibilityManager.getInstance(mContext).isEnabled()) {
6300                 mBeforeText = buffer.toString();
6301             }
6302 
6303             TextView.this.sendBeforeTextChanged(buffer, start, before, after);
6304         }
6305 
onTextChanged(CharSequence buffer, int start, int before, int after)6306         public void onTextChanged(CharSequence buffer, int start,
6307                                   int before, int after) {
6308             if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
6309                     + " before=" + before + " after=" + after + ": " + buffer);
6310             TextView.this.handleTextChanged(buffer, start, before, after);
6311 
6312             if (AccessibilityManager.getInstance(mContext).isEnabled() &&
6313                     (isFocused() || isSelected() &&
6314                     isShown())) {
6315                 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
6316                 mBeforeText = null;
6317             }
6318         }
6319 
afterTextChanged(Editable buffer)6320         public void afterTextChanged(Editable buffer) {
6321             if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
6322             TextView.this.sendAfterTextChanged(buffer);
6323 
6324             if (MetaKeyKeyListener.getMetaState(buffer,
6325                                  MetaKeyKeyListener.META_SELECTING) != 0) {
6326                 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
6327             }
6328         }
6329 
onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)6330         public void onSpanChanged(Spannable buf,
6331                                   Object what, int s, int e, int st, int en) {
6332             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
6333                     + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
6334             TextView.this.spanChange(buf, what, s, st, e, en);
6335         }
6336 
onSpanAdded(Spannable buf, Object what, int s, int e)6337         public void onSpanAdded(Spannable buf, Object what, int s, int e) {
6338             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
6339                     + " what=" + what + ": " + buf);
6340             TextView.this.spanChange(buf, what, -1, s, -1, e);
6341         }
6342 
onSpanRemoved(Spannable buf, Object what, int s, int e)6343         public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
6344             if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
6345                     + " what=" + what + ": " + buf);
6346             TextView.this.spanChange(buf, what, s, -1, e, -1);
6347         }
6348     }
6349 
makeBlink()6350     private void makeBlink() {
6351         if (!mCursorVisible) {
6352             if (mBlink != null) {
6353                 mBlink.removeCallbacks(mBlink);
6354             }
6355 
6356             return;
6357         }
6358 
6359         if (mBlink == null)
6360             mBlink = new Blink(this);
6361 
6362         mBlink.removeCallbacks(mBlink);
6363         mBlink.postAtTime(mBlink, mShowCursor + BLINK);
6364     }
6365 
6366     @Override
onStartTemporaryDetach()6367     public void onStartTemporaryDetach() {
6368         mTemporaryDetach = true;
6369     }
6370 
6371     @Override
onFinishTemporaryDetach()6372     public void onFinishTemporaryDetach() {
6373         mTemporaryDetach = false;
6374     }
6375 
6376     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)6377     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
6378         if (mTemporaryDetach) {
6379             // If we are temporarily in the detach state, then do nothing.
6380             super.onFocusChanged(focused, direction, previouslyFocusedRect);
6381             return;
6382         }
6383 
6384         mShowCursor = SystemClock.uptimeMillis();
6385 
6386         ensureEndedBatchEdit();
6387 
6388         if (focused) {
6389             int selStart = getSelectionStart();
6390             int selEnd = getSelectionEnd();
6391 
6392             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
6393                 boolean selMoved = mSelectionMoved;
6394 
6395                 if (mMovement != null) {
6396                     mMovement.onTakeFocus(this, (Spannable) mText, direction);
6397                 }
6398 
6399                 if (mSelectAllOnFocus) {
6400                     Selection.setSelection((Spannable) mText, 0, mText.length());
6401                 }
6402 
6403                 if (selMoved && selStart >= 0 && selEnd >= 0) {
6404                     /*
6405                      * Someone intentionally set the selection, so let them
6406                      * do whatever it is that they wanted to do instead of
6407                      * the default on-focus behavior.  We reset the selection
6408                      * here instead of just skipping the onTakeFocus() call
6409                      * because some movement methods do something other than
6410                      * just setting the selection in theirs and we still
6411                      * need to go through that path.
6412                      */
6413 
6414                     Selection.setSelection((Spannable) mText, selStart, selEnd);
6415                 }
6416                 mTouchFocusSelected = true;
6417             }
6418 
6419             mFrozenWithFocus = false;
6420             mSelectionMoved = false;
6421 
6422             if (mText instanceof Spannable) {
6423                 Spannable sp = (Spannable) mText;
6424                 MetaKeyKeyListener.resetMetaState(sp);
6425             }
6426 
6427             makeBlink();
6428 
6429             if (mError != null) {
6430                 showError();
6431             }
6432         } else {
6433             if (mError != null) {
6434                 hideError();
6435             }
6436             // Don't leave us in the middle of a batch edit.
6437             onEndBatchEdit();
6438         }
6439 
6440         startStopMarquee(focused);
6441 
6442         if (mTransformation != null) {
6443             mTransformation.onFocusChanged(this, mText, focused, direction,
6444                                            previouslyFocusedRect);
6445         }
6446 
6447         super.onFocusChanged(focused, direction, previouslyFocusedRect);
6448     }
6449 
6450     @Override
onWindowFocusChanged(boolean hasWindowFocus)6451     public void onWindowFocusChanged(boolean hasWindowFocus) {
6452         super.onWindowFocusChanged(hasWindowFocus);
6453 
6454         if (hasWindowFocus) {
6455             if (mBlink != null) {
6456                 mBlink.uncancel();
6457 
6458                 if (isFocused()) {
6459                     mShowCursor = SystemClock.uptimeMillis();
6460                     makeBlink();
6461                 }
6462             }
6463         } else {
6464             if (mBlink != null) {
6465                 mBlink.cancel();
6466             }
6467             // Don't leave us in the middle of a batch edit.
6468             onEndBatchEdit();
6469             if (mInputContentType != null) {
6470                 mInputContentType.enterDown = false;
6471             }
6472         }
6473 
6474         startStopMarquee(hasWindowFocus);
6475     }
6476 
6477     /**
6478      * Use {@link BaseInputConnection#removeComposingSpans
6479      * BaseInputConnection.removeComposingSpans()} to remove any IME composing
6480      * state from this text view.
6481      */
clearComposingText()6482     public void clearComposingText() {
6483         if (mText instanceof Spannable) {
6484             BaseInputConnection.removeComposingSpans((Spannable)mText);
6485         }
6486     }
6487 
6488     @Override
setSelected(boolean selected)6489     public void setSelected(boolean selected) {
6490         boolean wasSelected = isSelected();
6491 
6492         super.setSelected(selected);
6493 
6494         if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6495             if (selected) {
6496                 startMarquee();
6497             } else {
6498                 stopMarquee();
6499             }
6500         }
6501     }
6502 
6503     class CommitSelectionReceiver extends ResultReceiver {
6504         int mNewStart;
6505         int mNewEnd;
6506 
CommitSelectionReceiver()6507         CommitSelectionReceiver() {
6508             super(getHandler());
6509         }
6510 
onReceiveResult(int resultCode, Bundle resultData)6511         protected void onReceiveResult(int resultCode, Bundle resultData) {
6512             if (resultCode != InputMethodManager.RESULT_SHOWN) {
6513                 final int len = mText.length();
6514                 if (mNewStart > len) {
6515                     mNewStart = len;
6516                 }
6517                 if (mNewEnd > len) {
6518                     mNewEnd = len;
6519                 }
6520                 Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
6521             }
6522         }
6523     }
6524 
6525     @Override
onTouchEvent(MotionEvent event)6526     public boolean onTouchEvent(MotionEvent event) {
6527         final int action = event.getAction();
6528         if (action == MotionEvent.ACTION_DOWN) {
6529             // Reset this state; it will be re-set if super.onTouchEvent
6530             // causes focus to move to the view.
6531             mTouchFocusSelected = false;
6532         }
6533 
6534         final boolean superResult = super.onTouchEvent(event);
6535 
6536         /*
6537          * Don't handle the release after a long press, because it will
6538          * move the selection away from whatever the menu action was
6539          * trying to affect.
6540          */
6541         if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
6542             mEatTouchRelease = false;
6543             return superResult;
6544         }
6545 
6546         if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
6547 
6548             if (action == MotionEvent.ACTION_DOWN) {
6549                 mScrolled = false;
6550             }
6551 
6552             boolean handled = false;
6553 
6554             int oldSelStart = Selection.getSelectionStart(mText);
6555             int oldSelEnd = Selection.getSelectionEnd(mText);
6556 
6557             if (mMovement != null) {
6558                 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
6559             }
6560 
6561             if (mText instanceof Editable && onCheckIsTextEditor()) {
6562                 if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
6563                     InputMethodManager imm = (InputMethodManager)
6564                             getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
6565 
6566                     // This is going to be gross...  if tapping on the text view
6567                     // causes the IME to be displayed, we don't want the selection
6568                     // to change.  But the selection has already changed, and
6569                     // we won't know right away whether the IME is getting
6570                     // displayed, so...
6571 
6572                     int newSelStart = Selection.getSelectionStart(mText);
6573                     int newSelEnd = Selection.getSelectionEnd(mText);
6574                     CommitSelectionReceiver csr = null;
6575                     if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
6576                         csr = new CommitSelectionReceiver();
6577                         csr.mNewStart = newSelStart;
6578                         csr.mNewEnd = newSelEnd;
6579                     }
6580 
6581                     if (imm.showSoftInput(this, 0, csr) && csr != null) {
6582                         // The IME might get shown -- revert to the old
6583                         // selection, and change to the new when we finally
6584                         // find out of it is okay.
6585                         Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
6586                         handled = true;
6587                     }
6588                 }
6589             }
6590 
6591             if (handled) {
6592                 return true;
6593             }
6594         }
6595 
6596         return superResult;
6597     }
6598 
6599     /**
6600      * Returns true, only while processing a touch gesture, if the initial
6601      * touch down event caused focus to move to the text view and as a result
6602      * its selection changed.  Only valid while processing the touch gesture
6603      * of interest.
6604      */
didTouchFocusSelect()6605     public boolean didTouchFocusSelect() {
6606         return mTouchFocusSelected;
6607     }
6608 
6609     @Override
cancelLongPress()6610     public void cancelLongPress() {
6611         super.cancelLongPress();
6612         mScrolled = true;
6613     }
6614 
6615     @Override
onTrackballEvent(MotionEvent event)6616     public boolean onTrackballEvent(MotionEvent event) {
6617         if (mMovement != null && mText instanceof Spannable &&
6618             mLayout != null) {
6619             if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
6620                 return true;
6621             }
6622         }
6623 
6624         return super.onTrackballEvent(event);
6625     }
6626 
setScroller(Scroller s)6627     public void setScroller(Scroller s) {
6628         mScroller = s;
6629     }
6630 
6631     private static class Blink extends Handler implements Runnable {
6632         private WeakReference<TextView> mView;
6633         private boolean mCancelled;
6634 
Blink(TextView v)6635         public Blink(TextView v) {
6636             mView = new WeakReference<TextView>(v);
6637         }
6638 
run()6639         public void run() {
6640             if (mCancelled) {
6641                 return;
6642             }
6643 
6644             removeCallbacks(Blink.this);
6645 
6646             TextView tv = mView.get();
6647 
6648             if (tv != null && tv.isFocused()) {
6649                 int st = Selection.getSelectionStart(tv.mText);
6650                 int en = Selection.getSelectionEnd(tv.mText);
6651 
6652                 if (st == en && st >= 0 && en >= 0) {
6653                     if (tv.mLayout != null) {
6654                         tv.invalidateCursorPath();
6655                     }
6656 
6657                     postAtTime(this, SystemClock.uptimeMillis() + BLINK);
6658                 }
6659             }
6660         }
6661 
cancel()6662         void cancel() {
6663             if (!mCancelled) {
6664                 removeCallbacks(Blink.this);
6665                 mCancelled = true;
6666             }
6667         }
6668 
uncancel()6669         void uncancel() {
6670             mCancelled = false;
6671         }
6672     }
6673 
6674     @Override
getLeftFadingEdgeStrength()6675     protected float getLeftFadingEdgeStrength() {
6676         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6677             if (mMarquee != null && !mMarquee.isStopped()) {
6678                 final Marquee marquee = mMarquee;
6679                 if (marquee.shouldDrawLeftFade()) {
6680                     return marquee.mScroll / getHorizontalFadingEdgeLength();
6681                 } else {
6682                     return 0.0f;
6683                 }
6684             } else if (getLineCount() == 1) {
6685                 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6686                     case Gravity.LEFT:
6687                         return 0.0f;
6688                     case Gravity.RIGHT:
6689                         return (mLayout.getLineRight(0) - (mRight - mLeft) -
6690                                 getCompoundPaddingLeft() - getCompoundPaddingRight() -
6691                                 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
6692                     case Gravity.CENTER_HORIZONTAL:
6693                         return 0.0f;
6694                 }
6695             }
6696         }
6697         return super.getLeftFadingEdgeStrength();
6698     }
6699 
6700     @Override
getRightFadingEdgeStrength()6701     protected float getRightFadingEdgeStrength() {
6702         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6703             if (mMarquee != null && !mMarquee.isStopped()) {
6704                 final Marquee marquee = mMarquee;
6705                 return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
6706             } else if (getLineCount() == 1) {
6707                 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
6708                     case Gravity.LEFT:
6709                         final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
6710                                 getCompoundPaddingRight();
6711                         final float lineWidth = mLayout.getLineWidth(0);
6712                         return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
6713                     case Gravity.RIGHT:
6714                         return 0.0f;
6715                     case Gravity.CENTER_HORIZONTAL:
6716                         return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
6717                                 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
6718                                 getHorizontalFadingEdgeLength();
6719                 }
6720             }
6721         }
6722         return super.getRightFadingEdgeStrength();
6723     }
6724 
6725     @Override
computeHorizontalScrollRange()6726     protected int computeHorizontalScrollRange() {
6727         if (mLayout != null)
6728             return mLayout.getWidth();
6729 
6730         return super.computeHorizontalScrollRange();
6731     }
6732 
6733     @Override
computeVerticalScrollRange()6734     protected int computeVerticalScrollRange() {
6735         if (mLayout != null)
6736             return mLayout.getHeight();
6737 
6738         return super.computeVerticalScrollRange();
6739     }
6740 
6741     @Override
computeVerticalScrollExtent()6742     protected int computeVerticalScrollExtent() {
6743         return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
6744     }
6745 
6746     public enum BufferType {
6747         NORMAL, SPANNABLE, EDITABLE,
6748     }
6749 
6750     /**
6751      * Returns the TextView_textColor attribute from the
6752      * Resources.StyledAttributes, if set, or the TextAppearance_textColor
6753      * from the TextView_textAppearance attribute, if TextView_textColor
6754      * was not set directly.
6755      */
getTextColors(Context context, TypedArray attrs)6756     public static ColorStateList getTextColors(Context context, TypedArray attrs) {
6757         ColorStateList colors;
6758         colors = attrs.getColorStateList(com.android.internal.R.styleable.
6759                                          TextView_textColor);
6760 
6761         if (colors == null) {
6762             int ap = attrs.getResourceId(com.android.internal.R.styleable.
6763                                          TextView_textAppearance, -1);
6764             if (ap != -1) {
6765                 TypedArray appearance;
6766                 appearance = context.obtainStyledAttributes(ap,
6767                                             com.android.internal.R.styleable.TextAppearance);
6768                 colors = appearance.getColorStateList(com.android.internal.R.styleable.
6769                                                   TextAppearance_textColor);
6770                 appearance.recycle();
6771             }
6772         }
6773 
6774         return colors;
6775     }
6776 
6777     /**
6778      * Returns the default color from the TextView_textColor attribute
6779      * from the AttributeSet, if set, or the default color from the
6780      * TextAppearance_textColor from the TextView_textAppearance attribute,
6781      * if TextView_textColor was not set directly.
6782      */
getTextColor(Context context, TypedArray attrs, int def)6783     public static int getTextColor(Context context,
6784                                    TypedArray attrs,
6785                                    int def) {
6786         ColorStateList colors = getTextColors(context, attrs);
6787 
6788         if (colors == null) {
6789             return def;
6790         } else {
6791             return colors.getDefaultColor();
6792         }
6793     }
6794 
6795     @Override
onKeyShortcut(int keyCode, KeyEvent event)6796     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
6797         switch (keyCode) {
6798         case KeyEvent.KEYCODE_A:
6799             if (canSelectAll()) {
6800                 return onTextContextMenuItem(ID_SELECT_ALL);
6801             }
6802 
6803             break;
6804 
6805         case KeyEvent.KEYCODE_X:
6806             if (canCut()) {
6807                 return onTextContextMenuItem(ID_CUT);
6808             }
6809 
6810             break;
6811 
6812         case KeyEvent.KEYCODE_C:
6813             if (canCopy()) {
6814                 return onTextContextMenuItem(ID_COPY);
6815             }
6816 
6817             break;
6818 
6819         case KeyEvent.KEYCODE_V:
6820             if (canPaste()) {
6821                 return onTextContextMenuItem(ID_PASTE);
6822             }
6823 
6824             break;
6825         }
6826 
6827         return super.onKeyShortcut(keyCode, event);
6828     }
6829 
canSelectAll()6830     private boolean canSelectAll() {
6831         if (mText instanceof Spannable && mText.length() != 0 &&
6832             mMovement != null && mMovement.canSelectArbitrarily()) {
6833             return true;
6834         }
6835 
6836         return false;
6837     }
6838 
canSelectText()6839     private boolean canSelectText() {
6840         if (mText instanceof Spannable && mText.length() != 0 &&
6841             mMovement != null && mMovement.canSelectArbitrarily()) {
6842             return true;
6843         }
6844 
6845         return false;
6846     }
6847 
canCut()6848     private boolean canCut() {
6849         if (mTransformation instanceof PasswordTransformationMethod) {
6850             return false;
6851         }
6852 
6853         if (mText.length() > 0 && getSelectionStart() >= 0) {
6854             if (mText instanceof Editable && mInput != null) {
6855                 return true;
6856             }
6857         }
6858 
6859         return false;
6860     }
6861 
canCopy()6862     private boolean canCopy() {
6863         if (mTransformation instanceof PasswordTransformationMethod) {
6864             return false;
6865         }
6866 
6867         if (mText.length() > 0 && getSelectionStart() >= 0) {
6868             return true;
6869         }
6870 
6871         return false;
6872     }
6873 
canPaste()6874     private boolean canPaste() {
6875         if (mText instanceof Editable && mInput != null &&
6876             getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
6877             ClipboardManager clip = (ClipboardManager)getContext()
6878                     .getSystemService(Context.CLIPBOARD_SERVICE);
6879             if (clip.hasText()) {
6880                 return true;
6881             }
6882         }
6883 
6884         return false;
6885     }
6886 
6887     /**
6888      * Returns a word to add to the dictionary from the context menu,
6889      * or null if there is no cursor or no word at the cursor.
6890      */
getWordForDictionary()6891     private String getWordForDictionary() {
6892         /*
6893          * Quick return if the input type is one where adding words
6894          * to the dictionary doesn't make any sense.
6895          */
6896         int klass = mInputType & InputType.TYPE_MASK_CLASS;
6897         if (klass == InputType.TYPE_CLASS_NUMBER ||
6898             klass == InputType.TYPE_CLASS_PHONE ||
6899             klass == InputType.TYPE_CLASS_DATETIME) {
6900             return null;
6901         }
6902 
6903         int variation = mInputType & InputType.TYPE_MASK_VARIATION;
6904         if (variation == InputType.TYPE_TEXT_VARIATION_URI ||
6905             variation == InputType.TYPE_TEXT_VARIATION_PASSWORD ||
6906             variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
6907             variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
6908             variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
6909             return null;
6910         }
6911 
6912         int end = getSelectionEnd();
6913 
6914         if (end < 0) {
6915             return null;
6916         }
6917 
6918         int start = end;
6919         int len = mText.length();
6920 
6921         for (; start > 0; start--) {
6922             char c = mTransformed.charAt(start - 1);
6923             int type = Character.getType(c);
6924 
6925             if (c != '\'' &&
6926                 type != Character.UPPERCASE_LETTER &&
6927                 type != Character.LOWERCASE_LETTER &&
6928                 type != Character.TITLECASE_LETTER &&
6929                 type != Character.MODIFIER_LETTER &&
6930                 type != Character.DECIMAL_DIGIT_NUMBER) {
6931                 break;
6932             }
6933         }
6934 
6935         for (; end < len; end++) {
6936             char c = mTransformed.charAt(end);
6937             int type = Character.getType(c);
6938 
6939             if (c != '\'' &&
6940                 type != Character.UPPERCASE_LETTER &&
6941                 type != Character.LOWERCASE_LETTER &&
6942                 type != Character.TITLECASE_LETTER &&
6943                 type != Character.MODIFIER_LETTER &&
6944                 type != Character.DECIMAL_DIGIT_NUMBER) {
6945                 break;
6946             }
6947         }
6948 
6949         boolean hasLetter = false;
6950         for (int i = start; i < end; i++) {
6951             if (Character.isLetter(mTransformed.charAt(i))) {
6952                 hasLetter = true;
6953                 break;
6954             }
6955         }
6956         if (!hasLetter) {
6957             return null;
6958         }
6959 
6960         if (start == end) {
6961             return null;
6962         }
6963 
6964         if (end - start > 48) {
6965             return null;
6966         }
6967 
6968         return TextUtils.substring(mTransformed, start, end);
6969     }
6970 
6971     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)6972     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
6973         boolean isPassword =
6974             (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
6975             (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
6976 
6977         if (!isPassword) {
6978             CharSequence text = getText();
6979             if (TextUtils.isEmpty(text)) {
6980                 text = getHint();
6981             }
6982             if (!TextUtils.isEmpty(text)) {
6983                 if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
6984                     text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
6985                 }
6986                 event.getText().add(text);
6987             }
6988         } else {
6989             event.setPassword(isPassword);
6990         }
6991         return false;
6992     }
6993 
sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)6994     void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
6995             int fromIndex, int removedCount, int addedCount) {
6996         AccessibilityEvent event =
6997             AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
6998         event.setFromIndex(fromIndex);
6999         event.setRemovedCount(removedCount);
7000         event.setAddedCount(addedCount);
7001         event.setBeforeText(beforeText);
7002         sendAccessibilityEventUnchecked(event);
7003     }
7004 
7005     @Override
onCreateContextMenu(ContextMenu menu)7006     protected void onCreateContextMenu(ContextMenu menu) {
7007         super.onCreateContextMenu(menu);
7008         boolean added = false;
7009 
7010         if (!isFocused()) {
7011             if (isFocusable() && mInput != null) {
7012                 if (canCopy()) {
7013                     MenuHandler handler = new MenuHandler();
7014                     int name = com.android.internal.R.string.copyAll;
7015 
7016                     menu.add(0, ID_COPY, 0, name).
7017                         setOnMenuItemClickListener(handler).
7018                         setAlphabeticShortcut('c');
7019                     menu.setHeaderTitle(com.android.internal.R.string.
7020                         editTextMenuTitle);
7021                 }
7022             }
7023 
7024             return;
7025         }
7026 
7027         MenuHandler handler = new MenuHandler();
7028 
7029         if (canSelectAll()) {
7030             menu.add(0, ID_SELECT_ALL, 0,
7031                     com.android.internal.R.string.selectAll).
7032                 setOnMenuItemClickListener(handler).
7033                 setAlphabeticShortcut('a');
7034             added = true;
7035         }
7036 
7037         boolean selection = getSelectionStart() != getSelectionEnd();
7038 
7039         if (canSelectText()) {
7040             if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
7041                 menu.add(0, ID_STOP_SELECTING_TEXT, 0,
7042                         com.android.internal.R.string.stopSelectingText).
7043                     setOnMenuItemClickListener(handler);
7044                 added = true;
7045             } else {
7046                 menu.add(0, ID_START_SELECTING_TEXT, 0,
7047                         com.android.internal.R.string.selectText).
7048                     setOnMenuItemClickListener(handler);
7049                 added = true;
7050             }
7051         }
7052 
7053         if (canCut()) {
7054             int name;
7055             if (selection) {
7056                 name = com.android.internal.R.string.cut;
7057             } else {
7058                 name = com.android.internal.R.string.cutAll;
7059             }
7060 
7061             menu.add(0, ID_CUT, 0, name).
7062                 setOnMenuItemClickListener(handler).
7063                 setAlphabeticShortcut('x');
7064             added = true;
7065         }
7066 
7067         if (canCopy()) {
7068             int name;
7069             if (selection) {
7070                 name = com.android.internal.R.string.copy;
7071             } else {
7072                 name = com.android.internal.R.string.copyAll;
7073             }
7074 
7075             menu.add(0, ID_COPY, 0, name).
7076                 setOnMenuItemClickListener(handler).
7077                 setAlphabeticShortcut('c');
7078             added = true;
7079         }
7080 
7081         if (canPaste()) {
7082             menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
7083                     setOnMenuItemClickListener(handler).
7084                     setAlphabeticShortcut('v');
7085             added = true;
7086         }
7087 
7088         if (mText instanceof Spanned) {
7089             int selStart = getSelectionStart();
7090             int selEnd = getSelectionEnd();
7091 
7092             int min = Math.min(selStart, selEnd);
7093             int max = Math.max(selStart, selEnd);
7094 
7095             URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7096                                                         URLSpan.class);
7097             if (urls.length == 1) {
7098                 menu.add(0, ID_COPY_URL, 0,
7099                          com.android.internal.R.string.copyUrl).
7100                             setOnMenuItemClickListener(handler);
7101                 added = true;
7102             }
7103         }
7104 
7105         if (isInputMethodTarget()) {
7106             menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
7107                     setOnMenuItemClickListener(handler);
7108             added = true;
7109         }
7110 
7111         String word = getWordForDictionary();
7112         if (word != null) {
7113             menu.add(1, ID_ADD_TO_DICTIONARY, 0,
7114                      getContext().getString(com.android.internal.R.string.addToDictionary, word)).
7115                     setOnMenuItemClickListener(handler);
7116             added = true;
7117 
7118         }
7119 
7120         if (added) {
7121             menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
7122         }
7123     }
7124 
7125     /**
7126      * Returns whether this text view is a current input method target.  The
7127      * default implementation just checks with {@link InputMethodManager}.
7128      */
isInputMethodTarget()7129     public boolean isInputMethodTarget() {
7130         InputMethodManager imm = InputMethodManager.peekInstance();
7131         return imm != null && imm.isActive(this);
7132     }
7133 
7134     private static final int ID_SELECT_ALL = android.R.id.selectAll;
7135     private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
7136     private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
7137     private static final int ID_CUT = android.R.id.cut;
7138     private static final int ID_COPY = android.R.id.copy;
7139     private static final int ID_PASTE = android.R.id.paste;
7140     private static final int ID_COPY_URL = android.R.id.copyUrl;
7141     private static final int ID_SWITCH_INPUT_METHOD = android.R.id.switchInputMethod;
7142     private static final int ID_ADD_TO_DICTIONARY = android.R.id.addToDictionary;
7143 
7144     private class MenuHandler implements MenuItem.OnMenuItemClickListener {
onMenuItemClick(MenuItem item)7145         public boolean onMenuItemClick(MenuItem item) {
7146             return onTextContextMenuItem(item.getItemId());
7147         }
7148     }
7149 
7150     /**
7151      * Called when a context menu option for the text view is selected.  Currently
7152      * this will be one of: {@link android.R.id#selectAll},
7153      * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
7154      * {@link android.R.id#cut}, {@link android.R.id#copy},
7155      * {@link android.R.id#paste}, {@link android.R.id#copyUrl},
7156      * or {@link android.R.id#switchInputMethod}.
7157      */
onTextContextMenuItem(int id)7158     public boolean onTextContextMenuItem(int id) {
7159         int selStart = getSelectionStart();
7160         int selEnd = getSelectionEnd();
7161 
7162         if (!isFocused()) {
7163             selStart = 0;
7164             selEnd = mText.length();
7165         }
7166 
7167         int min = Math.min(selStart, selEnd);
7168         int max = Math.max(selStart, selEnd);
7169 
7170         if (min < 0) {
7171             min = 0;
7172         }
7173         if (max < 0) {
7174             max = 0;
7175         }
7176 
7177         ClipboardManager clip = (ClipboardManager)getContext()
7178                 .getSystemService(Context.CLIPBOARD_SERVICE);
7179 
7180         switch (id) {
7181             case ID_SELECT_ALL:
7182                 Selection.setSelection((Spannable) mText, 0,
7183                         mText.length());
7184                 return true;
7185 
7186             case ID_START_SELECTING_TEXT:
7187                 MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
7188                 return true;
7189 
7190             case ID_STOP_SELECTING_TEXT:
7191                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7192                 Selection.setSelection((Spannable) mText, getSelectionEnd());
7193                 return true;
7194 
7195             case ID_CUT:
7196                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7197 
7198                 if (min == max) {
7199                     min = 0;
7200                     max = mText.length();
7201                 }
7202 
7203                 clip.setText(mTransformed.subSequence(min, max));
7204                 ((Editable) mText).delete(min, max);
7205                 return true;
7206 
7207             case ID_COPY:
7208                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7209 
7210                 if (min == max) {
7211                     min = 0;
7212                     max = mText.length();
7213                 }
7214 
7215                 clip.setText(mTransformed.subSequence(min, max));
7216                 return true;
7217 
7218             case ID_PASTE:
7219                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7220 
7221                 CharSequence paste = clip.getText();
7222 
7223                 if (paste != null) {
7224                     Selection.setSelection((Spannable) mText, max);
7225                     ((Editable) mText).replace(min, max, paste);
7226                 }
7227 
7228                 return true;
7229 
7230             case ID_COPY_URL:
7231                 MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
7232 
7233                 URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
7234                                                        URLSpan.class);
7235                 if (urls.length == 1) {
7236                     clip.setText(urls[0].getURL());
7237                 }
7238 
7239                 return true;
7240 
7241             case ID_SWITCH_INPUT_METHOD:
7242                 InputMethodManager imm = InputMethodManager.peekInstance();
7243                 if (imm != null) {
7244                     imm.showInputMethodPicker();
7245                 }
7246                 return true;
7247 
7248             case ID_ADD_TO_DICTIONARY:
7249                 String word = getWordForDictionary();
7250 
7251                 if (word != null) {
7252                     Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
7253                     i.putExtra("word", word);
7254                     i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
7255                     getContext().startActivity(i);
7256                 }
7257 
7258                 return true;
7259             }
7260 
7261         return false;
7262     }
7263 
performLongClick()7264     public boolean performLongClick() {
7265         if (super.performLongClick()) {
7266             mEatTouchRelease = true;
7267             return true;
7268         }
7269 
7270         return false;
7271     }
7272 
7273     @ViewDebug.ExportedProperty
7274     private CharSequence            mText;
7275     private CharSequence            mTransformed;
7276     private BufferType              mBufferType = BufferType.NORMAL;
7277 
7278     private int                     mInputType = EditorInfo.TYPE_NULL;
7279     private CharSequence            mHint;
7280     private Layout                  mHintLayout;
7281 
7282     private KeyListener             mInput;
7283 
7284     private MovementMethod          mMovement;
7285     private TransformationMethod    mTransformation;
7286     private ChangeWatcher           mChangeWatcher;
7287 
7288     private ArrayList<TextWatcher>  mListeners = null;
7289 
7290     // display attributes
7291     private TextPaint               mTextPaint;
7292     private boolean                 mUserSetTextScaleX;
7293     private Paint                   mHighlightPaint;
7294     private int                     mHighlightColor = 0xFFBBDDFF;
7295     private Layout                  mLayout;
7296 
7297     private long                    mShowCursor;
7298     private Blink                   mBlink;
7299     private boolean                 mCursorVisible = true;
7300 
7301     private boolean                 mSelectAllOnFocus = false;
7302 
7303     private int                     mGravity = Gravity.TOP | Gravity.LEFT;
7304     private boolean                 mHorizontallyScrolling;
7305 
7306     private int                     mAutoLinkMask;
7307     private boolean                 mLinksClickable = true;
7308 
7309     private float                   mSpacingMult = 1;
7310     private float                   mSpacingAdd = 0;
7311 
7312     private static final int        LINES = 1;
7313     private static final int        EMS = LINES;
7314     private static final int        PIXELS = 2;
7315 
7316     private int                     mMaximum = Integer.MAX_VALUE;
7317     private int                     mMaxMode = LINES;
7318     private int                     mMinimum = 0;
7319     private int                     mMinMode = LINES;
7320 
7321     private int                     mMaxWidth = Integer.MAX_VALUE;
7322     private int                     mMaxWidthMode = PIXELS;
7323     private int                     mMinWidth = 0;
7324     private int                     mMinWidthMode = PIXELS;
7325 
7326     private boolean                 mSingleLine;
7327     private int                     mDesiredHeightAtMeasure = -1;
7328     private boolean                 mIncludePad = true;
7329 
7330     // tmp primitives, so we don't alloc them on each draw
7331     private Path                    mHighlightPath;
7332     private boolean                 mHighlightPathBogus = true;
7333     private static final RectF      sTempRect = new RectF();
7334 
7335     // XXX should be much larger
7336     private static final int        VERY_WIDE = 16384;
7337 
7338     private static final int        BLINK = 500;
7339 
7340     private static final int ANIMATED_SCROLL_GAP = 250;
7341     private long mLastScroll;
7342     private Scroller mScroller = null;
7343 
7344     private BoringLayout.Metrics mBoring;
7345     private BoringLayout.Metrics mHintBoring;
7346 
7347     private BoringLayout mSavedLayout, mSavedHintLayout;
7348 
7349     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
7350     private InputFilter[] mFilters = NO_FILTERS;
7351     private static final Spanned EMPTY_SPANNED = new SpannedString("");
7352 }
7353