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