2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.widget;
19 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
22 import android.annotation.ColorInt;
23 import android.annotation.DrawableRes;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.Size;
27 import android.annotation.StringRes;
28 import android.annotation.StyleRes;
29 import android.annotation.XmlRes;
30 import android.app.Activity;
31 import android.app.assist.AssistStructure;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.UndoManager;
37 import android.content.res.ColorStateList;
38 import android.content.res.CompatibilityInfo;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.content.res.TypedArray;
42 import android.content.res.XmlResourceParser;
43 import android.graphics.Canvas;
44 import android.graphics.Insets;
45 import android.graphics.Paint;
46 import android.graphics.Path;
47 import android.graphics.PorterDuff;
48 import android.graphics.Rect;
49 import android.graphics.RectF;
50 import android.graphics.Typeface;
51 import android.graphics.drawable.Drawable;
52 import android.os.AsyncTask;
53 import android.os.Bundle;
54 import android.os.Parcel;
55 import android.os.Parcelable;
56 import android.os.ParcelableParcel;
57 import android.os.SystemClock;
58 import android.os.UserHandle;
59 import android.provider.Settings;
60 import android.text.BoringLayout;
61 import android.text.DynamicLayout;
62 import android.text.Editable;
63 import android.text.GetChars;
64 import android.text.GraphicsOperations;
65 import android.text.InputFilter;
66 import android.text.InputType;
67 import android.text.Layout;
68 import android.text.ParcelableSpan;
69 import android.text.Selection;
70 import android.text.SpanWatcher;
71 import android.text.Spannable;
72 import android.text.SpannableString;
73 import android.text.SpannableStringBuilder;
74 import android.text.Spanned;
75 import android.text.SpannedString;
76 import android.text.StaticLayout;
77 import android.text.TextDirectionHeuristic;
78 import android.text.TextDirectionHeuristics;
79 import android.text.TextPaint;
80 import android.text.TextUtils;
81 import android.text.TextUtils.TruncateAt;
82 import android.text.TextWatcher;
83 import android.text.method.AllCapsTransformationMethod;
84 import android.text.method.ArrowKeyMovementMethod;
85 import android.text.method.DateKeyListener;
86 import android.text.method.DateTimeKeyListener;
87 import android.text.method.DialerKeyListener;
88 import android.text.method.DigitsKeyListener;
89 import android.text.method.KeyListener;
90 import android.text.method.LinkMovementMethod;
91 import android.text.method.MetaKeyKeyListener;
92 import android.text.method.MovementMethod;
93 import android.text.method.PasswordTransformationMethod;
94 import android.text.method.SingleLineTransformationMethod;
95 import android.text.method.TextKeyListener;
96 import android.text.method.TimeKeyListener;
97 import android.text.method.TransformationMethod;
98 import android.text.method.TransformationMethod2;
99 import android.text.method.WordIterator;
100 import android.text.style.CharacterStyle;
101 import android.text.style.ClickableSpan;
102 import android.text.style.ParagraphStyle;
103 import android.text.style.SpellCheckSpan;
104 import android.text.style.SuggestionSpan;
105 import android.text.style.URLSpan;
106 import android.text.style.UpdateAppearance;
107 import android.text.util.Linkify;
108 import android.util.AttributeSet;
109 import android.util.LocaleList;
110 import android.util.Log;
111 import android.util.TypedValue;
112 import android.view.AccessibilityIterators.TextSegmentIterator;
113 import android.view.ActionMode;
114 import android.view.Choreographer;
115 import android.view.DragEvent;
116 import android.view.Gravity;
117 import android.view.HapticFeedbackConstants;
118 import android.view.KeyCharacterMap;
119 import android.view.KeyEvent;
120 import android.view.MotionEvent;
121 import android.view.PointerIcon;
122 import android.view.View;
123 import android.view.ViewConfiguration;
124 import android.view.ViewDebug;
125 import android.view.ViewGroup.LayoutParams;
126 import android.view.ViewHierarchyEncoder;
127 import android.view.ViewParent;
128 import android.view.ViewRootImpl;
129 import android.view.ViewStructure;
130 import android.view.ViewTreeObserver;
131 import android.view.accessibility.AccessibilityEvent;
132 import android.view.accessibility.AccessibilityManager;
133 import android.view.accessibility.AccessibilityNodeInfo;
134 import android.view.animation.AnimationUtils;
135 import android.view.inputmethod.BaseInputConnection;
136 import android.view.inputmethod.CompletionInfo;
137 import android.view.inputmethod.CorrectionInfo;
138 import android.view.inputmethod.EditorInfo;
139 import android.view.inputmethod.ExtractedText;
140 import android.view.inputmethod.ExtractedTextRequest;
141 import android.view.inputmethod.InputConnection;
142 import android.view.inputmethod.InputMethodManager;
143 import android.view.textservice.SpellCheckerSubtype;
144 import android.view.textservice.TextServicesManager;
145 import android.widget.RemoteViews.RemoteView;
147 import com.android.internal.annotations.VisibleForTesting;
148 import com.android.internal.util.FastMath;
149 import com.android.internal.widget.EditableInputConnection;
151 import org.xmlpull.v1.XmlPullParserException;
153 import java.io.IOException;
154 import java.lang.ref.WeakReference;
155 import java.util.ArrayList;
156 import java.util.Locale;
159 * Displays text to the user and optionally allows them to edit it. A TextView
160 * is a complete text editor, however the basic class is configured to not
161 * allow editing; see {@link EditText} for a subclass that configures the text
165 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
166 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
167 * android:textIsSelectable} to "true" or call
168 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
169 * allows users to make selection gestures in the TextView, which in turn triggers the system's
170 * built-in copy/paste controls.
172 * <b>XML attributes</b>
174 * See {@link android.R.styleable#TextView TextView Attributes},
175 * {@link android.R.styleable#View View Attributes}
177 * @attr ref android.R.styleable#TextView_text
178 * @attr ref android.R.styleable#TextView_bufferType
179 * @attr ref android.R.styleable#TextView_hint
180 * @attr ref android.R.styleable#TextView_textColor
181 * @attr ref android.R.styleable#TextView_textColorHighlight
182 * @attr ref android.R.styleable#TextView_textColorHint
183 * @attr ref android.R.styleable#TextView_textAppearance
184 * @attr ref android.R.styleable#TextView_textColorLink
185 * @attr ref android.R.styleable#TextView_textSize
186 * @attr ref android.R.styleable#TextView_textScaleX
187 * @attr ref android.R.styleable#TextView_fontFamily
188 * @attr ref android.R.styleable#TextView_typeface
189 * @attr ref android.R.styleable#TextView_textStyle
190 * @attr ref android.R.styleable#TextView_cursorVisible
191 * @attr ref android.R.styleable#TextView_maxLines
192 * @attr ref android.R.styleable#TextView_maxHeight
193 * @attr ref android.R.styleable#TextView_lines
194 * @attr ref android.R.styleable#TextView_height
195 * @attr ref android.R.styleable#TextView_minLines
196 * @attr ref android.R.styleable#TextView_minHeight
197 * @attr ref android.R.styleable#TextView_maxEms
198 * @attr ref android.R.styleable#TextView_maxWidth
199 * @attr ref android.R.styleable#TextView_ems
200 * @attr ref android.R.styleable#TextView_width
201 * @attr ref android.R.styleable#TextView_minEms
202 * @attr ref android.R.styleable#TextView_minWidth
203 * @attr ref android.R.styleable#TextView_gravity
204 * @attr ref android.R.styleable#TextView_scrollHorizontally
205 * @attr ref android.R.styleable#TextView_password
206 * @attr ref android.R.styleable#TextView_singleLine
207 * @attr ref android.R.styleable#TextView_selectAllOnFocus
208 * @attr ref android.R.styleable#TextView_includeFontPadding
209 * @attr ref android.R.styleable#TextView_maxLength
210 * @attr ref android.R.styleable#TextView_shadowColor
211 * @attr ref android.R.styleable#TextView_shadowDx
212 * @attr ref android.R.styleable#TextView_shadowDy
213 * @attr ref android.R.styleable#TextView_shadowRadius
214 * @attr ref android.R.styleable#TextView_autoLink
215 * @attr ref android.R.styleable#TextView_linksClickable
216 * @attr ref android.R.styleable#TextView_numeric
217 * @attr ref android.R.styleable#TextView_digits
218 * @attr ref android.R.styleable#TextView_phoneNumber
219 * @attr ref android.R.styleable#TextView_inputMethod
220 * @attr ref android.R.styleable#TextView_capitalize
221 * @attr ref android.R.styleable#TextView_autoText
222 * @attr ref android.R.styleable#TextView_editable
223 * @attr ref android.R.styleable#TextView_freezesText
224 * @attr ref android.R.styleable#TextView_ellipsize
225 * @attr ref android.R.styleable#TextView_drawableTop
226 * @attr ref android.R.styleable#TextView_drawableBottom
227 * @attr ref android.R.styleable#TextView_drawableRight
228 * @attr ref android.R.styleable#TextView_drawableLeft
229 * @attr ref android.R.styleable#TextView_drawableStart
230 * @attr ref android.R.styleable#TextView_drawableEnd
231 * @attr ref android.R.styleable#TextView_drawablePadding
232 * @attr ref android.R.styleable#TextView_drawableTint
233 * @attr ref android.R.styleable#TextView_drawableTintMode
234 * @attr ref android.R.styleable#TextView_lineSpacingExtra
235 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
236 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
237 * @attr ref android.R.styleable#TextView_inputType
238 * @attr ref android.R.styleable#TextView_imeOptions
239 * @attr ref android.R.styleable#TextView_privateImeOptions
240 * @attr ref android.R.styleable#TextView_imeActionLabel
241 * @attr ref android.R.styleable#TextView_imeActionId
242 * @attr ref android.R.styleable#TextView_editorExtras
243 * @attr ref android.R.styleable#TextView_elegantTextHeight
244 * @attr ref android.R.styleable#TextView_letterSpacing
245 * @attr ref android.R.styleable#TextView_fontFeatureSettings
246 * @attr ref android.R.styleable#TextView_breakStrategy
247 * @attr ref android.R.styleable#TextView_hyphenationFrequency
250 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
251 static final String LOG_TAG = "TextView";
252 static final boolean DEBUG_EXTRACT = false;
254 // Enum for the "typeface" XML parameter.
255 // TODO: How can we get this from the XML instead of hardcoding it here?
256 private static final int SANS = 1;
257 private static final int SERIF = 2;
258 private static final int MONOSPACE = 3;
260 // Bitfield for the "numeric" XML parameter.
261 // TODO: How can we get this from the XML instead of hardcoding it here?
262 private static final int SIGNED = 2;
263 private static final int DECIMAL = 4;
266 * Draw marquee text with fading edges as usual
268 private static final int MARQUEE_FADE_NORMAL = 0;
271 * Draw marquee text as ellipsize end while inactive instead of with the fade.
272 * (Useful for devices where the fade can be expensive if overdone)
274 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
277 * Draw marquee text with fading edges because it is currently active/animating.
279 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
281 private static final int LINES = 1;
282 private static final int EMS = LINES;
283 private static final int PIXELS = 2;
285 private static final RectF TEMP_RECTF = new RectF();
287 // XXX should be much larger
288 private static final int VERY_WIDE = 1024*1024;
289 private static final int ANIMATED_SCROLL_GAP = 250;
291 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
292 private static final Spanned EMPTY_SPANNED = new SpannedString("");
294 private static final int CHANGE_WATCHER_PRIORITY = 100;
296 // New state used to change background based on whether this TextView is multiline.
297 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
299 // Accessibility action to share selected text.
300 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
305 // Accessibility action start id for "process text" actions.
306 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
311 static final int PROCESS_TEXT_REQUEST_CODE = 100;
314 * Return code of {@link #doKeyDown}.
316 private static final int KEY_EVENT_NOT_HANDLED = 0;
317 private static final int KEY_EVENT_HANDLED = -1;
318 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
319 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
321 // System wide time for last cut, copy or text changed action.
322 static long sLastCutCopyOrTextChangedTime;
324 private ColorStateList mTextColor;
325 private ColorStateList mHintTextColor;
326 private ColorStateList mLinkTextColor;
327 @ViewDebug.ExportedProperty(category = "text")
328 private int mCurTextColor;
329 private int mCurHintTextColor;
330 private boolean mFreezesText;
331 private boolean mDispatchTemporaryDetach;
333 /** Whether this view is temporarily detached from the parent view. */
334 boolean mTemporaryDetach;
336 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
337 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
339 private float mShadowRadius, mShadowDx, mShadowDy;
340 private int mShadowColor;
342 private boolean mPreDrawRegistered;
343 private boolean mPreDrawListenerDetached;
345 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
346 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
347 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
348 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
349 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
350 private boolean mPreventDefaultMovement;
352 private TextUtils.TruncateAt mEllipsize;
354 static class Drawables {
355 static final int LEFT = 0;
356 static final int TOP = 1;
357 static final int RIGHT = 2;
358 static final int BOTTOM = 3;
360 static final int DRAWABLE_NONE = -1;
361 static final int DRAWABLE_RIGHT = 0;
362 static final int DRAWABLE_LEFT = 1;
364 final Rect mCompoundRect = new Rect();
366 final Drawable[] mShowing = new Drawable[4];
368 ColorStateList mTintList;
369 PorterDuff.Mode mTintMode;
371 boolean mHasTintMode;
373 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
374 Drawable mDrawableLeftInitial, mDrawableRightInitial;
376 boolean mIsRtlCompatibilityMode;
379 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
380 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
382 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
383 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
385 int mDrawablePadding;
387 int mDrawableSaved = DRAWABLE_NONE;
389 public Drawables(Context context) {
390 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
391 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
392 !context.getApplicationInfo().hasRtlSupport());
396 public void resolveWithLayoutDirection(int layoutDirection) {
397 // First reset "left" and "right" drawables to their initial values
398 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
399 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
401 if (mIsRtlCompatibilityMode) {
402 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
403 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
404 mShowing[Drawables.LEFT] = mDrawableStart;
405 mDrawableSizeLeft = mDrawableSizeStart;
406 mDrawableHeightLeft = mDrawableHeightStart;
408 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
409 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
410 mShowing[Drawables.RIGHT] = mDrawableEnd;
411 mDrawableSizeRight = mDrawableSizeEnd;
412 mDrawableHeightRight = mDrawableHeightEnd;
415 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
416 // drawable if and only if they have been defined
417 switch(layoutDirection) {
418 case LAYOUT_DIRECTION_RTL:
420 mShowing[Drawables.RIGHT] = mDrawableStart;
421 mDrawableSizeRight = mDrawableSizeStart;
422 mDrawableHeightRight = mDrawableHeightStart;
424 mShowing[Drawables.LEFT] = mDrawableEnd;
425 mDrawableSizeLeft = mDrawableSizeEnd;
426 mDrawableHeightLeft = mDrawableHeightEnd;
430 case LAYOUT_DIRECTION_LTR:
433 mShowing[Drawables.LEFT] = mDrawableStart;
434 mDrawableSizeLeft = mDrawableSizeStart;
435 mDrawableHeightLeft = mDrawableHeightStart;
437 mShowing[Drawables.RIGHT] = mDrawableEnd;
438 mDrawableSizeRight = mDrawableSizeEnd;
439 mDrawableHeightRight = mDrawableHeightEnd;
444 applyErrorDrawableIfNeeded(layoutDirection);
445 updateDrawablesLayoutDirection(layoutDirection);
448 private void updateDrawablesLayoutDirection(int layoutDirection) {
449 for (Drawable dr : mShowing) {
451 dr.setLayoutDirection(layoutDirection);
456 public void setErrorDrawable(Drawable dr, TextView tv) {
457 if (mDrawableError != dr && mDrawableError != null) {
458 mDrawableError.setCallback(null);
462 if (mDrawableError != null) {
463 final Rect compoundRect = mCompoundRect;
464 final int[] state = tv.getDrawableState();
466 mDrawableError.setState(state);
467 mDrawableError.copyBounds(compoundRect);
468 mDrawableError.setCallback(tv);
469 mDrawableSizeError = compoundRect.width();
470 mDrawableHeightError = compoundRect.height();
472 mDrawableSizeError = mDrawableHeightError = 0;
476 private void applyErrorDrawableIfNeeded(int layoutDirection) {
477 // first restore the initial state if needed
478 switch (mDrawableSaved) {
480 mShowing[Drawables.LEFT] = mDrawableTemp;
481 mDrawableSizeLeft = mDrawableSizeTemp;
482 mDrawableHeightLeft = mDrawableHeightTemp;
485 mShowing[Drawables.RIGHT] = mDrawableTemp;
486 mDrawableSizeRight = mDrawableSizeTemp;
487 mDrawableHeightRight = mDrawableHeightTemp;
492 // then, if needed, assign the Error drawable to the correct location
493 if (mDrawableError != null) {
494 switch(layoutDirection) {
495 case LAYOUT_DIRECTION_RTL:
496 mDrawableSaved = DRAWABLE_LEFT;
498 mDrawableTemp = mShowing[Drawables.LEFT];
499 mDrawableSizeTemp = mDrawableSizeLeft;
500 mDrawableHeightTemp = mDrawableHeightLeft;
502 mShowing[Drawables.LEFT] = mDrawableError;
503 mDrawableSizeLeft = mDrawableSizeError;
504 mDrawableHeightLeft = mDrawableHeightError;
506 case LAYOUT_DIRECTION_LTR:
508 mDrawableSaved = DRAWABLE_RIGHT;
510 mDrawableTemp = mShowing[Drawables.RIGHT];
511 mDrawableSizeTemp = mDrawableSizeRight;
512 mDrawableHeightTemp = mDrawableHeightRight;
514 mShowing[Drawables.RIGHT] = mDrawableError;
515 mDrawableSizeRight = mDrawableSizeError;
516 mDrawableHeightRight = mDrawableHeightError;
523 Drawables mDrawables;
525 private CharWrapper mCharWrapper;
527 private Marquee mMarquee;
528 private boolean mRestartMarquee;
530 private int mMarqueeRepeatLimit = 3;
532 private int mLastLayoutDirection = -1;
535 * On some devices the fading edges add a performance penalty if used
536 * extensively in the same layout. This mode indicates how the marquee
537 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
539 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
542 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
543 * the layout that should be used when the mode switches.
545 private Layout mSavedMarqueeModeLayout;
547 @ViewDebug.ExportedProperty(category = "text")
548 private CharSequence mText;
549 private CharSequence mTransformed;
550 private BufferType mBufferType = BufferType.NORMAL;
552 private CharSequence mHint;
553 private Layout mHintLayout;
555 private MovementMethod mMovement;
557 private TransformationMethod mTransformation;
558 private boolean mAllowTransformationLengthChange;
559 private ChangeWatcher mChangeWatcher;
561 private ArrayList<TextWatcher> mListeners;
563 // display attributes
564 private final TextPaint mTextPaint;
565 private boolean mUserSetTextScaleX;
566 private Layout mLayout;
567 private boolean mLocalesChanged = false;
569 @ViewDebug.ExportedProperty(category = "text")
570 private int mGravity = Gravity.TOP | Gravity.START;
571 private boolean mHorizontallyScrolling;
573 private int mAutoLinkMask;
574 private boolean mLinksClickable = true;
576 private float mSpacingMult = 1.0f;
577 private float mSpacingAdd = 0.0f;
579 private int mBreakStrategy;
580 private int mHyphenationFrequency;
582 private int mMaximum = Integer.MAX_VALUE;
583 private int mMaxMode = LINES;
584 private int mMinimum = 0;
585 private int mMinMode = LINES;
587 private int mOldMaximum = mMaximum;
588 private int mOldMaxMode = mMaxMode;
590 private int mMaxWidth = Integer.MAX_VALUE;
591 private int mMaxWidthMode = PIXELS;
592 private int mMinWidth = 0;
593 private int mMinWidthMode = PIXELS;
595 private boolean mSingleLine;
596 private int mDesiredHeightAtMeasure = -1;
597 private boolean mIncludePad = true;
598 private int mDeferScroll = -1;
600 // tmp primitives, so we don't alloc them on each draw
601 private Rect mTempRect;
602 private long mLastScroll;
603 private Scroller mScroller;
605 private BoringLayout.Metrics mBoring, mHintBoring;
606 private BoringLayout mSavedLayout, mSavedHintLayout;
608 private TextDirectionHeuristic mTextDir;
610 private InputFilter[] mFilters = NO_FILTERS;
612 private volatile Locale mCurrentSpellCheckerLocaleCache;
614 // It is possible to have a selection even when mEditor is null (programmatically set, like when
615 // a link is pressed). These highlight-related fields do not go in mEditor.
616 int mHighlightColor = 0x6633B5E5;
617 private Path mHighlightPath;
618 private final Paint mHighlightPaint;
619 private boolean mHighlightPathBogus = true;
621 private boolean mFirstTouch = false;
622 private long mLastTouchUpTime = 0;
624 // Although these fields are specific to editable text, they are not added to Editor because
625 // they are defined by the TextView's style and are theme-dependent.
626 int mCursorDrawableRes;
627 // These six fields, could be moved to Editor, since we know their default values and we
628 // could condition the creation of the Editor to a non standard value. This is however
629 // brittle since the hardcoded values here (such as
630 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
631 // default style is modified.
632 int mTextSelectHandleLeftRes;
633 int mTextSelectHandleRightRes;
634 int mTextSelectHandleRes;
635 int mTextEditSuggestionItemLayout;
636 int mTextEditSuggestionContainerLayout;
637 int mTextEditSuggestionHighlightStyle;
640 * EditText specific data, created on demand when one of the Editor fields is used.
641 * See {@link #createEditorIfNeeded()}.
643 private Editor mEditor;
646 * Kick-start the font cache for the zygote process (to pay the cost of
647 * initializing freetype for our default font only once).
650 Paint p = new Paint();
651 p.setAntiAlias(true);
652 // We don't care about the result, just the side-effect of measuring.
657 * Interface definition for a callback to be invoked when an action is
658 * performed on the editor.
660 public interface OnEditorActionListener {
662 * Called when an action is being performed.
664 * @param v The view that was clicked.
665 * @param actionId Identifier of the action. This will be either the
666 * identifier you supplied, or {@link EditorInfo#IME_NULL
667 * EditorInfo.IME_NULL} if being called due to the enter key
669 * @param event If triggered by an enter key, this is the event;
670 * otherwise, this is null.
671 * @return Return true if you have consumed the action, else false.
673 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
676 public TextView(Context context) {
680 public TextView(Context context, @Nullable AttributeSet attrs) {
681 this(context, attrs, com.android.internal.R.attr.textViewStyle);
684 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
685 this(context, attrs, defStyleAttr, 0);
688 @SuppressWarnings("deprecation")
690 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
691 super(context, attrs, defStyleAttr, defStyleRes);
695 final Resources res = getResources();
696 final CompatibilityInfo compat = res.getCompatibilityInfo();
698 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
699 mTextPaint.density = res.getDisplayMetrics().density;
700 mTextPaint.setCompatibilityScaling(compat.applicationScale);
702 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
703 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
705 mMovement = getDefaultMovementMethod();
707 mTransformation = null;
709 int textColorHighlight = 0;
710 ColorStateList textColor = null;
711 ColorStateList textColorHint = null;
712 ColorStateList textColorLink = null;
714 String fontFamily = null;
715 boolean fontFamilyExplicit = false;
716 int typefaceIndex = -1;
718 boolean allCaps = false;
720 float dx = 0, dy = 0, r = 0;
721 boolean elegant = false;
722 float letterSpacing = 0;
723 String fontFeatureSettings = null;
724 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
725 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
727 final Resources.Theme theme = context.getTheme();
730 * Look the appearance up without checking first if it exists because
731 * almost every TextView has one and it greatly simplifies the logic
732 * to be able to parse the appearance first and then let specific tags
733 * for this View override it.
735 TypedArray a = theme.obtainStyledAttributes(attrs,
736 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
737 TypedArray appearance = null;
738 int ap = a.getResourceId(
739 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
742 appearance = theme.obtainStyledAttributes(
743 ap, com.android.internal.R.styleable.TextAppearance);
745 if (appearance != null) {
746 int n = appearance.getIndexCount();
747 for (int i = 0; i < n; i++) {
748 int attr = appearance.getIndex(i);
751 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
752 textColorHighlight = appearance.getColor(attr, textColorHighlight);
755 case com.android.internal.R.styleable.TextAppearance_textColor:
756 textColor = appearance.getColorStateList(attr);
759 case com.android.internal.R.styleable.TextAppearance_textColorHint:
760 textColorHint = appearance.getColorStateList(attr);
763 case com.android.internal.R.styleable.TextAppearance_textColorLink:
764 textColorLink = appearance.getColorStateList(attr);
767 case com.android.internal.R.styleable.TextAppearance_textSize:
768 textSize = appearance.getDimensionPixelSize(attr, textSize);
771 case com.android.internal.R.styleable.TextAppearance_typeface:
772 typefaceIndex = appearance.getInt(attr, -1);
775 case com.android.internal.R.styleable.TextAppearance_fontFamily:
776 fontFamily = appearance.getString(attr);
779 case com.android.internal.R.styleable.TextAppearance_textStyle:
780 styleIndex = appearance.getInt(attr, -1);
783 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
784 allCaps = appearance.getBoolean(attr, false);
787 case com.android.internal.R.styleable.TextAppearance_shadowColor:
788 shadowcolor = appearance.getInt(attr, 0);
791 case com.android.internal.R.styleable.TextAppearance_shadowDx:
792 dx = appearance.getFloat(attr, 0);
795 case com.android.internal.R.styleable.TextAppearance_shadowDy:
796 dy = appearance.getFloat(attr, 0);
799 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
800 r = appearance.getFloat(attr, 0);
803 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
804 elegant = appearance.getBoolean(attr, false);
807 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
808 letterSpacing = appearance.getFloat(attr, 0);
811 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
812 fontFeatureSettings = appearance.getString(attr);
817 appearance.recycle();
820 boolean editable = getDefaultEditable();
821 CharSequence inputMethod = null;
823 CharSequence digits = null;
824 boolean phone = false;
825 boolean autotext = false;
828 boolean selectallonfocus = false;
829 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
830 drawableBottom = null, drawableStart = null, drawableEnd = null;
831 ColorStateList drawableTint = null;
832 PorterDuff.Mode drawableTintMode = null;
833 int drawablePadding = 0;
835 boolean singleLine = false;
837 CharSequence text = "";
838 CharSequence hint = null;
839 boolean password = false;
840 int inputType = EditorInfo.TYPE_NULL;
842 a = theme.obtainStyledAttributes(
843 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
845 int n = a.getIndexCount();
846 for (int i = 0; i < n; i++) {
847 int attr = a.getIndex(i);
850 case com.android.internal.R.styleable.TextView_editable:
851 editable = a.getBoolean(attr, editable);
854 case com.android.internal.R.styleable.TextView_inputMethod:
855 inputMethod = a.getText(attr);
858 case com.android.internal.R.styleable.TextView_numeric:
859 numeric = a.getInt(attr, numeric);
862 case com.android.internal.R.styleable.TextView_digits:
863 digits = a.getText(attr);
866 case com.android.internal.R.styleable.TextView_phoneNumber:
867 phone = a.getBoolean(attr, phone);
870 case com.android.internal.R.styleable.TextView_autoText:
871 autotext = a.getBoolean(attr, autotext);
874 case com.android.internal.R.styleable.TextView_capitalize:
875 autocap = a.getInt(attr, autocap);
878 case com.android.internal.R.styleable.TextView_bufferType:
879 buffertype = a.getInt(attr, buffertype);
882 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
883 selectallonfocus = a.getBoolean(attr, selectallonfocus);
886 case com.android.internal.R.styleable.TextView_autoLink:
887 mAutoLinkMask = a.getInt(attr, 0);
890 case com.android.internal.R.styleable.TextView_linksClickable:
891 mLinksClickable = a.getBoolean(attr, true);
894 case com.android.internal.R.styleable.TextView_drawableLeft:
895 drawableLeft = a.getDrawable(attr);
898 case com.android.internal.R.styleable.TextView_drawableTop:
899 drawableTop = a.getDrawable(attr);
902 case com.android.internal.R.styleable.TextView_drawableRight:
903 drawableRight = a.getDrawable(attr);
906 case com.android.internal.R.styleable.TextView_drawableBottom:
907 drawableBottom = a.getDrawable(attr);
910 case com.android.internal.R.styleable.TextView_drawableStart:
911 drawableStart = a.getDrawable(attr);
914 case com.android.internal.R.styleable.TextView_drawableEnd:
915 drawableEnd = a.getDrawable(attr);
918 case com.android.internal.R.styleable.TextView_drawableTint:
919 drawableTint = a.getColorStateList(attr);
922 case com.android.internal.R.styleable.TextView_drawableTintMode:
923 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
926 case com.android.internal.R.styleable.TextView_drawablePadding:
927 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
930 case com.android.internal.R.styleable.TextView_maxLines:
931 setMaxLines(a.getInt(attr, -1));
934 case com.android.internal.R.styleable.TextView_maxHeight:
935 setMaxHeight(a.getDimensionPixelSize(attr, -1));
938 case com.android.internal.R.styleable.TextView_lines:
939 setLines(a.getInt(attr, -1));
942 case com.android.internal.R.styleable.TextView_height:
943 setHeight(a.getDimensionPixelSize(attr, -1));
946 case com.android.internal.R.styleable.TextView_minLines:
947 setMinLines(a.getInt(attr, -1));
950 case com.android.internal.R.styleable.TextView_minHeight:
951 setMinHeight(a.getDimensionPixelSize(attr, -1));
954 case com.android.internal.R.styleable.TextView_maxEms:
955 setMaxEms(a.getInt(attr, -1));
958 case com.android.internal.R.styleable.TextView_maxWidth:
959 setMaxWidth(a.getDimensionPixelSize(attr, -1));
962 case com.android.internal.R.styleable.TextView_ems:
963 setEms(a.getInt(attr, -1));
966 case com.android.internal.R.styleable.TextView_width:
967 setWidth(a.getDimensionPixelSize(attr, -1));
970 case com.android.internal.R.styleable.TextView_minEms:
971 setMinEms(a.getInt(attr, -1));
974 case com.android.internal.R.styleable.TextView_minWidth:
975 setMinWidth(a.getDimensionPixelSize(attr, -1));
978 case com.android.internal.R.styleable.TextView_gravity:
979 setGravity(a.getInt(attr, -1));
982 case com.android.internal.R.styleable.TextView_hint:
983 hint = a.getText(attr);
986 case com.android.internal.R.styleable.TextView_text:
987 text = a.getText(attr);
990 case com.android.internal.R.styleable.TextView_scrollHorizontally:
991 if (a.getBoolean(attr, false)) {
992 setHorizontallyScrolling(true);
996 case com.android.internal.R.styleable.TextView_singleLine:
997 singleLine = a.getBoolean(attr, singleLine);
1000 case com.android.internal.R.styleable.TextView_ellipsize:
1001 ellipsize = a.getInt(attr, ellipsize);
1004 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1005 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1008 case com.android.internal.R.styleable.TextView_includeFontPadding:
1009 if (!a.getBoolean(attr, true)) {
1010 setIncludeFontPadding(false);
1014 case com.android.internal.R.styleable.TextView_cursorVisible:
1015 if (!a.getBoolean(attr, true)) {
1016 setCursorVisible(false);
1020 case com.android.internal.R.styleable.TextView_maxLength:
1021 maxlength = a.getInt(attr, -1);
1024 case com.android.internal.R.styleable.TextView_textScaleX:
1025 setTextScaleX(a.getFloat(attr, 1.0f));
1028 case com.android.internal.R.styleable.TextView_freezesText:
1029 mFreezesText = a.getBoolean(attr, false);
1032 case com.android.internal.R.styleable.TextView_shadowColor:
1033 shadowcolor = a.getInt(attr, 0);
1036 case com.android.internal.R.styleable.TextView_shadowDx:
1037 dx = a.getFloat(attr, 0);
1040 case com.android.internal.R.styleable.TextView_shadowDy:
1041 dy = a.getFloat(attr, 0);
1044 case com.android.internal.R.styleable.TextView_shadowRadius:
1045 r = a.getFloat(attr, 0);
1048 case com.android.internal.R.styleable.TextView_enabled:
1049 setEnabled(a.getBoolean(attr, isEnabled()));
1052 case com.android.internal.R.styleable.TextView_textColorHighlight:
1053 textColorHighlight = a.getColor(attr, textColorHighlight);
1056 case com.android.internal.R.styleable.TextView_textColor:
1057 textColor = a.getColorStateList(attr);
1060 case com.android.internal.R.styleable.TextView_textColorHint:
1061 textColorHint = a.getColorStateList(attr);
1064 case com.android.internal.R.styleable.TextView_textColorLink:
1065 textColorLink = a.getColorStateList(attr);
1068 case com.android.internal.R.styleable.TextView_textSize:
1069 textSize = a.getDimensionPixelSize(attr, textSize);
1072 case com.android.internal.R.styleable.TextView_typeface:
1073 typefaceIndex = a.getInt(attr, typefaceIndex);
1076 case com.android.internal.R.styleable.TextView_textStyle:
1077 styleIndex = a.getInt(attr, styleIndex);
1080 case com.android.internal.R.styleable.TextView_fontFamily:
1081 fontFamily = a.getString(attr);
1082 fontFamilyExplicit = true;
1085 case com.android.internal.R.styleable.TextView_password:
1086 password = a.getBoolean(attr, password);
1089 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1090 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1093 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1094 mSpacingMult = a.getFloat(attr, mSpacingMult);
1097 case com.android.internal.R.styleable.TextView_inputType:
1098 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1101 case com.android.internal.R.styleable.TextView_allowUndo:
1102 createEditorIfNeeded();
1103 mEditor.mAllowUndo = a.getBoolean(attr, true);
1106 case com.android.internal.R.styleable.TextView_imeOptions:
1107 createEditorIfNeeded();
1108 mEditor.createInputContentTypeIfNeeded();
1109 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1110 mEditor.mInputContentType.imeOptions);
1113 case com.android.internal.R.styleable.TextView_imeActionLabel:
1114 createEditorIfNeeded();
1115 mEditor.createInputContentTypeIfNeeded();
1116 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1119 case com.android.internal.R.styleable.TextView_imeActionId:
1120 createEditorIfNeeded();
1121 mEditor.createInputContentTypeIfNeeded();
1122 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1123 mEditor.mInputContentType.imeActionId);
1126 case com.android.internal.R.styleable.TextView_privateImeOptions:
1127 setPrivateImeOptions(a.getString(attr));
1130 case com.android.internal.R.styleable.TextView_editorExtras:
1132 setInputExtras(a.getResourceId(attr, 0));
1133 } catch (XmlPullParserException e) {
1134 Log.w(LOG_TAG, "Failure reading input extras", e);
1135 } catch (IOException e) {
1136 Log.w(LOG_TAG, "Failure reading input extras", e);
1140 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1141 mCursorDrawableRes = a.getResourceId(attr, 0);
1144 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1145 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1148 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1149 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1152 case com.android.internal.R.styleable.TextView_textSelectHandle:
1153 mTextSelectHandleRes = a.getResourceId(attr, 0);
1156 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1157 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1160 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1161 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1164 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1165 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1168 case com.android.internal.R.styleable.TextView_textIsSelectable:
1169 setTextIsSelectable(a.getBoolean(attr, false));
1172 case com.android.internal.R.styleable.TextView_textAllCaps:
1173 allCaps = a.getBoolean(attr, false);
1176 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1177 elegant = a.getBoolean(attr, false);
1180 case com.android.internal.R.styleable.TextView_letterSpacing:
1181 letterSpacing = a.getFloat(attr, 0);
1184 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1185 fontFeatureSettings = a.getString(attr);
1188 case com.android.internal.R.styleable.TextView_breakStrategy:
1189 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1192 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1193 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1199 BufferType bufferType = BufferType.EDITABLE;
1201 final int variation =
1202 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1203 final boolean passwordInputType = variation
1204 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1205 final boolean webPasswordInputType = variation
1206 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1207 final boolean numberPasswordInputType = variation
1208 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1210 if (inputMethod != null) {
1214 c = Class.forName(inputMethod.toString());
1215 } catch (ClassNotFoundException ex) {
1216 throw new RuntimeException(ex);
1220 createEditorIfNeeded();
1221 mEditor.mKeyListener = (KeyListener) c.newInstance();
1222 } catch (InstantiationException ex) {
1223 throw new RuntimeException(ex);
1224 } catch (IllegalAccessException ex) {
1225 throw new RuntimeException(ex);
1228 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1230 : mEditor.mKeyListener.getInputType();
1231 } catch (IncompatibleClassChangeError e) {
1232 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1234 } else if (digits != null) {
1235 createEditorIfNeeded();
1236 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1237 // If no input type was specified, we will default to generic
1238 // text, since we can't tell the IME about the set of digits
1239 // that was selected.
1240 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1241 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1242 } else if (inputType != EditorInfo.TYPE_NULL) {
1243 setInputType(inputType, true);
1244 // If set, the input type overrides what was set using the deprecated singleLine flag.
1245 singleLine = !isMultilineInputType(inputType);
1247 createEditorIfNeeded();
1248 mEditor.mKeyListener = DialerKeyListener.getInstance();
1249 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1250 } else if (numeric != 0) {
1251 createEditorIfNeeded();
1252 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1253 (numeric & DECIMAL) != 0);
1254 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1255 if ((numeric & SIGNED) != 0) {
1256 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1258 if ((numeric & DECIMAL) != 0) {
1259 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1261 mEditor.mInputType = inputType;
1262 } else if (autotext || autocap != -1) {
1263 TextKeyListener.Capitalize cap;
1265 inputType = EditorInfo.TYPE_CLASS_TEXT;
1269 cap = TextKeyListener.Capitalize.SENTENCES;
1270 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1274 cap = TextKeyListener.Capitalize.WORDS;
1275 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1279 cap = TextKeyListener.Capitalize.CHARACTERS;
1280 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1284 cap = TextKeyListener.Capitalize.NONE;
1288 createEditorIfNeeded();
1289 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1290 mEditor.mInputType = inputType;
1291 } else if (isTextSelectable()) {
1292 // Prevent text changes from keyboard.
1293 if (mEditor != null) {
1294 mEditor.mKeyListener = null;
1295 mEditor.mInputType = EditorInfo.TYPE_NULL;
1297 bufferType = BufferType.SPANNABLE;
1298 // So that selection can be changed using arrow keys and touch is handled.
1299 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1300 } else if (editable) {
1301 createEditorIfNeeded();
1302 mEditor.mKeyListener = TextKeyListener.getInstance();
1303 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1305 if (mEditor != null) mEditor.mKeyListener = null;
1307 switch (buffertype) {
1309 bufferType = BufferType.NORMAL;
1312 bufferType = BufferType.SPANNABLE;
1315 bufferType = BufferType.EDITABLE;
1320 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1321 webPasswordInputType, numberPasswordInputType);
1323 if (selectallonfocus) {
1324 createEditorIfNeeded();
1325 mEditor.mSelectAllOnFocus = true;
1327 if (bufferType == BufferType.NORMAL)
1328 bufferType = BufferType.SPANNABLE;
1331 // Set up the tint (if needed) before setting the drawables so that it
1332 // gets applied correctly.
1333 if (drawableTint != null || drawableTintMode != null) {
1334 if (mDrawables == null) {
1335 mDrawables = new Drawables(context);
1337 if (drawableTint != null) {
1338 mDrawables.mTintList = drawableTint;
1339 mDrawables.mHasTint = true;
1341 if (drawableTintMode != null) {
1342 mDrawables.mTintMode = drawableTintMode;
1343 mDrawables.mHasTintMode = true;
1347 // This call will save the initial left/right drawables
1348 setCompoundDrawablesWithIntrinsicBounds(
1349 drawableLeft, drawableTop, drawableRight, drawableBottom);
1350 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1351 setCompoundDrawablePadding(drawablePadding);
1353 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1354 // of lines of height are unchanged for multi-line TextViews.
1355 setInputTypeSingleLine(singleLine);
1356 applySingleLine(singleLine, singleLine, singleLine);
1358 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1359 ellipsize = 3; // END
1362 switch (ellipsize) {
1364 setEllipsize(TextUtils.TruncateAt.START);
1367 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1370 setEllipsize(TextUtils.TruncateAt.END);
1373 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1374 setHorizontalFadingEdgeEnabled(true);
1375 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1377 setHorizontalFadingEdgeEnabled(false);
1378 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1380 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1384 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1385 setHintTextColor(textColorHint);
1386 setLinkTextColor(textColorLink);
1387 if (textColorHighlight != 0) {
1388 setHighlightColor(textColorHighlight);
1390 setRawTextSize(textSize);
1391 setElegantTextHeight(elegant);
1392 setLetterSpacing(letterSpacing);
1393 setFontFeatureSettings(fontFeatureSettings);
1396 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1399 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1400 setTransformationMethod(PasswordTransformationMethod.getInstance());
1401 typefaceIndex = MONOSPACE;
1402 } else if (mEditor != null &&
1403 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1404 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1405 typefaceIndex = MONOSPACE;
1408 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1411 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1413 if (shadowcolor != 0) {
1414 setShadowLayer(r, dx, dy, shadowcolor);
1417 if (maxlength >= 0) {
1418 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1420 setFilters(NO_FILTERS);
1423 setText(text, bufferType);
1424 if (hint != null) setHint(hint);
1427 * Views are not normally focusable unless specified to be.
1428 * However, TextViews that have input or movement methods *are*
1429 * focusable by default.
1431 a = context.obtainStyledAttributes(
1432 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1434 boolean focusable = mMovement != null || getKeyListener() != null;
1435 boolean clickable = focusable || isClickable();
1436 boolean longClickable = focusable || isLongClickable();
1438 n = a.getIndexCount();
1439 for (int i = 0; i < n; i++) {
1440 int attr = a.getIndex(i);
1443 case com.android.internal.R.styleable.View_focusable:
1444 focusable = a.getBoolean(attr, focusable);
1447 case com.android.internal.R.styleable.View_clickable:
1448 clickable = a.getBoolean(attr, clickable);
1451 case com.android.internal.R.styleable.View_longClickable:
1452 longClickable = a.getBoolean(attr, longClickable);
1458 setFocusable(focusable);
1459 setClickable(clickable);
1460 setLongClickable(longClickable);
1462 if (mEditor != null) mEditor.prepareCursorControllers();
1464 // If not explicitly specified this view is important for accessibility.
1465 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1466 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1470 private int[] parseDimensionArray(TypedArray dimens) {
1471 if (dimens == null) {
1474 int[] result = new int[dimens.length()];
1475 for (int i = 0; i < result.length; i++) {
1476 result[i] = dimens.getDimensionPixelSize(i, 0);
1485 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1486 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1487 if (resultCode == Activity.RESULT_OK && data != null) {
1488 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1489 if (result != null) {
1490 if (isTextEditable()) {
1491 replaceSelectionWithText(result);
1493 if (result.length() > 0) {
1494 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1499 } else if (mText instanceof Spannable) {
1500 // Reset the selection.
1501 stopTextActionMode();
1502 Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
1505 if (mEditor.hasSelectionController()) {
1506 mEditor.startSelectionActionMode();
1511 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1513 if (familyName != null) {
1514 tf = Typeface.create(familyName, styleIndex);
1520 switch (typefaceIndex) {
1522 tf = Typeface.SANS_SERIF;
1526 tf = Typeface.SERIF;
1530 tf = Typeface.MONOSPACE;
1534 setTypeface(tf, styleIndex);
1537 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1538 boolean hasRelativeDrawables = (start != null) || (end != null);
1539 if (hasRelativeDrawables) {
1540 Drawables dr = mDrawables;
1542 mDrawables = dr = new Drawables(getContext());
1544 mDrawables.mOverride = true;
1545 final Rect compoundRect = dr.mCompoundRect;
1546 int[] state = getDrawableState();
1547 if (start != null) {
1548 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1549 start.setState(state);
1550 start.copyBounds(compoundRect);
1551 start.setCallback(this);
1553 dr.mDrawableStart = start;
1554 dr.mDrawableSizeStart = compoundRect.width();
1555 dr.mDrawableHeightStart = compoundRect.height();
1557 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1560 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1561 end.setState(state);
1562 end.copyBounds(compoundRect);
1563 end.setCallback(this);
1565 dr.mDrawableEnd = end;
1566 dr.mDrawableSizeEnd = compoundRect.width();
1567 dr.mDrawableHeightEnd = compoundRect.height();
1569 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1571 resetResolvedDrawables();
1573 applyCompoundDrawableTint();
1578 public void setEnabled(boolean enabled) {
1579 if (enabled == isEnabled()) {
1584 // Hide the soft input if the currently active TextView is disabled
1585 InputMethodManager imm = InputMethodManager.peekInstance();
1586 if (imm != null && imm.isActive(this)) {
1587 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1591 super.setEnabled(enabled);
1594 // Make sure IME is updated with current editor info.
1595 InputMethodManager imm = InputMethodManager.peekInstance();
1596 if (imm != null) imm.restartInput(this);
1599 // Will change text color
1600 if (mEditor != null) {
1601 mEditor.invalidateTextDisplayList();
1602 mEditor.prepareCursorControllers();
1604 // start or stop the cursor blinking as appropriate
1605 mEditor.makeBlink();
1610 * Sets the typeface and style in which the text should be displayed,
1611 * and turns on the fake bold and italic bits in the Paint if the
1612 * Typeface that you provided does not have all the bits in the
1613 * style that you specified.
1615 * @attr ref android.R.styleable#TextView_typeface
1616 * @attr ref android.R.styleable#TextView_textStyle
1618 public void setTypeface(Typeface tf, int style) {
1621 tf = Typeface.defaultFromStyle(style);
1623 tf = Typeface.create(tf, style);
1627 // now compute what (if any) algorithmic styling is needed
1628 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1629 int need = style & ~typefaceStyle;
1630 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1631 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1633 mTextPaint.setFakeBoldText(false);
1634 mTextPaint.setTextSkewX(0);
1640 * Subclasses override this to specify that they have a KeyListener
1641 * by default even if not specifically called for in the XML options.
1643 protected boolean getDefaultEditable() {
1648 * Subclasses override this to specify a default movement method.
1650 protected MovementMethod getDefaultMovementMethod() {
1655 * Return the text the TextView is displaying. If setText() was called with
1656 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1657 * the return value from this method to Spannable or Editable, respectively.
1659 * Note: The content of the return value should not be modified. If you want
1660 * a modifiable one, you should make your own copy first.
1662 * @attr ref android.R.styleable#TextView_text
1664 @ViewDebug.CapturedViewProperty
1665 public CharSequence getText() {
1670 * Returns the length, in characters, of the text managed by this TextView
1672 public int length() {
1673 return mText.length();
1677 * Return the text the TextView is displaying as an Editable object. If
1678 * the text is not editable, null is returned.
1682 public Editable getEditableText() {
1683 return (mText instanceof Editable) ? (Editable)mText : null;
1687 * @return the height of one standard line in pixels. Note that markup
1688 * within the text can cause individual lines to be taller or shorter
1689 * than this height, and the layout may contain additional first-
1690 * or last-line padding.
1692 public int getLineHeight() {
1693 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1697 * @return the Layout that is currently being used to display the text.
1698 * This can be null if the text or width has recently changes.
1700 public final Layout getLayout() {
1705 * @return the Layout that is currently being used to display the hint text.
1708 final Layout getHintLayout() {
1713 * Retrieve the {@link android.content.UndoManager} that is currently associated
1714 * with this TextView. By default there is no associated UndoManager, so null
1715 * is returned. One can be associated with the TextView through
1716 * {@link #setUndoManager(android.content.UndoManager, String)}
1720 public final UndoManager getUndoManager() {
1721 // TODO: Consider supporting a global undo manager.
1722 throw new UnsupportedOperationException("not implemented");
1730 public final Editor getEditorForTesting() {
1735 * Associate an {@link android.content.UndoManager} with this TextView. Once
1736 * done, all edit operations on the TextView will result in appropriate
1737 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1740 * @param undoManager The {@link android.content.UndoManager} to associate with
1741 * this TextView, or null to clear any existing association.
1742 * @param tag String tag identifying this particular TextView owner in the
1743 * UndoManager. This is used to keep the correct association with the
1744 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1748 public final void setUndoManager(UndoManager undoManager, String tag) {
1749 // TODO: Consider supporting a global undo manager. An implementation will need to:
1750 // * createEditorIfNeeded()
1751 // * Promote to BufferType.EDITABLE if needed.
1752 // * Update the UndoManager and UndoOwner.
1753 // Likewise it will need to be able to restore the default UndoManager.
1754 throw new UnsupportedOperationException("not implemented");
1758 * @return the current key listener for this TextView.
1759 * This will frequently be null for non-EditText TextViews.
1761 * @attr ref android.R.styleable#TextView_numeric
1762 * @attr ref android.R.styleable#TextView_digits
1763 * @attr ref android.R.styleable#TextView_phoneNumber
1764 * @attr ref android.R.styleable#TextView_inputMethod
1765 * @attr ref android.R.styleable#TextView_capitalize
1766 * @attr ref android.R.styleable#TextView_autoText
1768 public final KeyListener getKeyListener() {
1769 return mEditor == null ? null : mEditor.mKeyListener;
1773 * Sets the key listener to be used with this TextView. This can be null
1774 * to disallow user input. Note that this method has significant and
1775 * subtle interactions with soft keyboards and other input method:
1776 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1777 * for important details. Calling this method will replace the current
1778 * content type of the text view with the content type returned by the
1781 * Be warned that if you want a TextView with a key listener or movement
1782 * method not to be focusable, or if you want a TextView without a
1783 * key listener or movement method to be focusable, you must call
1784 * {@link #setFocusable} again after calling this to get the focusability
1785 * back the way you want it.
1787 * @attr ref android.R.styleable#TextView_numeric
1788 * @attr ref android.R.styleable#TextView_digits
1789 * @attr ref android.R.styleable#TextView_phoneNumber
1790 * @attr ref android.R.styleable#TextView_inputMethod
1791 * @attr ref android.R.styleable#TextView_capitalize
1792 * @attr ref android.R.styleable#TextView_autoText
1794 public void setKeyListener(KeyListener input) {
1795 setKeyListenerOnly(input);
1796 fixFocusableAndClickableSettings();
1798 if (input != null) {
1799 createEditorIfNeeded();
1801 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1802 } catch (IncompatibleClassChangeError e) {
1803 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1805 // Change inputType, without affecting transformation.
1806 // No need to applySingleLine since mSingleLine is unchanged.
1807 setInputTypeSingleLine(mSingleLine);
1809 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1812 InputMethodManager imm = InputMethodManager.peekInstance();
1813 if (imm != null) imm.restartInput(this);
1816 private void setKeyListenerOnly(KeyListener input) {
1817 if (mEditor == null && input == null) return; // null is the default value
1819 createEditorIfNeeded();
1820 if (mEditor.mKeyListener != input) {
1821 mEditor.mKeyListener = input;
1822 if (input != null && !(mText instanceof Editable)) {
1826 setFilters((Editable) mText, mFilters);
1831 * @return the movement method being used for this TextView.
1832 * This will frequently be null for non-EditText TextViews.
1834 public final MovementMethod getMovementMethod() {
1839 * Sets the movement method (arrow key handler) to be used for
1840 * this TextView. This can be null to disallow using the arrow keys
1841 * to move the cursor or scroll the view.
1843 * Be warned that if you want a TextView with a key listener or movement
1844 * method not to be focusable, or if you want a TextView without a
1845 * key listener or movement method to be focusable, you must call
1846 * {@link #setFocusable} again after calling this to get the focusability
1847 * back the way you want it.
1849 public final void setMovementMethod(MovementMethod movement) {
1850 if (mMovement != movement) {
1851 mMovement = movement;
1853 if (movement != null && !(mText instanceof Spannable)) {
1857 fixFocusableAndClickableSettings();
1859 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1861 if (mEditor != null) mEditor.prepareCursorControllers();
1865 private void fixFocusableAndClickableSettings() {
1866 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1869 setLongClickable(true);
1871 setFocusable(false);
1872 setClickable(false);
1873 setLongClickable(false);
1878 * @return the current transformation method for this TextView.
1879 * This will frequently be null except for single-line and password
1882 * @attr ref android.R.styleable#TextView_password
1883 * @attr ref android.R.styleable#TextView_singleLine
1885 public final TransformationMethod getTransformationMethod() {
1886 return mTransformation;
1890 * Sets the transformation that is applied to the text that this
1891 * TextView is displaying.
1893 * @attr ref android.R.styleable#TextView_password
1894 * @attr ref android.R.styleable#TextView_singleLine
1896 public final void setTransformationMethod(TransformationMethod method) {
1897 if (method == mTransformation) {
1898 // Avoid the setText() below if the transformation is
1902 if (mTransformation != null) {
1903 if (mText instanceof Spannable) {
1904 ((Spannable) mText).removeSpan(mTransformation);
1908 mTransformation = method;
1910 if (method instanceof TransformationMethod2) {
1911 TransformationMethod2 method2 = (TransformationMethod2) method;
1912 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1913 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1915 mAllowTransformationLengthChange = false;
1920 if (hasPasswordTransformationMethod()) {
1921 notifyViewAccessibilityStateChangedIfNeeded(
1922 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1927 * Returns the top padding of the view, plus space for the top
1930 public int getCompoundPaddingTop() {
1931 final Drawables dr = mDrawables;
1932 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
1935 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1940 * Returns the bottom padding of the view, plus space for the bottom
1943 public int getCompoundPaddingBottom() {
1944 final Drawables dr = mDrawables;
1945 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
1946 return mPaddingBottom;
1948 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1953 * Returns the left padding of the view, plus space for the left
1956 public int getCompoundPaddingLeft() {
1957 final Drawables dr = mDrawables;
1958 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
1959 return mPaddingLeft;
1961 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1966 * Returns the right padding of the view, plus space for the right
1969 public int getCompoundPaddingRight() {
1970 final Drawables dr = mDrawables;
1971 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
1972 return mPaddingRight;
1974 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1979 * Returns the start padding of the view, plus space for the start
1982 public int getCompoundPaddingStart() {
1984 switch(getLayoutDirection()) {
1986 case LAYOUT_DIRECTION_LTR:
1987 return getCompoundPaddingLeft();
1988 case LAYOUT_DIRECTION_RTL:
1989 return getCompoundPaddingRight();
1994 * Returns the end padding of the view, plus space for the end
1997 public int getCompoundPaddingEnd() {
1999 switch(getLayoutDirection()) {
2001 case LAYOUT_DIRECTION_LTR:
2002 return getCompoundPaddingRight();
2003 case LAYOUT_DIRECTION_RTL:
2004 return getCompoundPaddingLeft();
2009 * Returns the extended top padding of the view, including both the
2010 * top Drawable if any and any extra space to keep more than maxLines
2011 * of text from showing. It is only valid to call this after measuring.
2013 public int getExtendedPaddingTop() {
2014 if (mMaxMode != LINES) {
2015 return getCompoundPaddingTop();
2018 if (mLayout == null) {
2022 if (mLayout.getLineCount() <= mMaximum) {
2023 return getCompoundPaddingTop();
2026 int top = getCompoundPaddingTop();
2027 int bottom = getCompoundPaddingBottom();
2028 int viewht = getHeight() - top - bottom;
2029 int layoutht = mLayout.getLineTop(mMaximum);
2031 if (layoutht >= viewht) {
2035 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2036 if (gravity == Gravity.TOP) {
2038 } else if (gravity == Gravity.BOTTOM) {
2039 return top + viewht - layoutht;
2040 } else { // (gravity == Gravity.CENTER_VERTICAL)
2041 return top + (viewht - layoutht) / 2;
2046 * Returns the extended bottom padding of the view, including both the
2047 * bottom Drawable if any and any extra space to keep more than maxLines
2048 * of text from showing. It is only valid to call this after measuring.
2050 public int getExtendedPaddingBottom() {
2051 if (mMaxMode != LINES) {
2052 return getCompoundPaddingBottom();
2055 if (mLayout == null) {
2059 if (mLayout.getLineCount() <= mMaximum) {
2060 return getCompoundPaddingBottom();
2063 int top = getCompoundPaddingTop();
2064 int bottom = getCompoundPaddingBottom();
2065 int viewht = getHeight() - top - bottom;
2066 int layoutht = mLayout.getLineTop(mMaximum);
2068 if (layoutht >= viewht) {
2072 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2073 if (gravity == Gravity.TOP) {
2074 return bottom + viewht - layoutht;
2075 } else if (gravity == Gravity.BOTTOM) {
2077 } else { // (gravity == Gravity.CENTER_VERTICAL)
2078 return bottom + (viewht - layoutht) / 2;
2083 * Returns the total left padding of the view, including the left
2086 public int getTotalPaddingLeft() {
2087 return getCompoundPaddingLeft();
2091 * Returns the total right padding of the view, including the right
2094 public int getTotalPaddingRight() {
2095 return getCompoundPaddingRight();
2099 * Returns the total start padding of the view, including the start
2102 public int getTotalPaddingStart() {
2103 return getCompoundPaddingStart();
2107 * Returns the total end padding of the view, including the end
2110 public int getTotalPaddingEnd() {
2111 return getCompoundPaddingEnd();
2115 * Returns the total top padding of the view, including the top
2116 * Drawable if any, the extra space to keep more than maxLines
2117 * from showing, and the vertical offset for gravity, if any.
2119 public int getTotalPaddingTop() {
2120 return getExtendedPaddingTop() + getVerticalOffset(true);
2124 * Returns the total bottom padding of the view, including the bottom
2125 * Drawable if any, the extra space to keep more than maxLines
2126 * from showing, and the vertical offset for gravity, if any.
2128 public int getTotalPaddingBottom() {
2129 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2133 * Sets the Drawables (if any) to appear to the left of, above, to the
2134 * right of, and below the text. Use {@code null} if you do not want a
2135 * Drawable there. The Drawables must already have had
2136 * {@link Drawable#setBounds} called.
2138 * Calling this method will overwrite any Drawables previously set using
2139 * {@link #setCompoundDrawablesRelative} or related methods.
2141 * @attr ref android.R.styleable#TextView_drawableLeft
2142 * @attr ref android.R.styleable#TextView_drawableTop
2143 * @attr ref android.R.styleable#TextView_drawableRight
2144 * @attr ref android.R.styleable#TextView_drawableBottom
2146 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2147 @Nullable Drawable right, @Nullable Drawable bottom) {
2148 Drawables dr = mDrawables;
2150 // We're switching to absolute, discard relative.
2152 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2153 dr.mDrawableStart = null;
2154 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2155 dr.mDrawableEnd = null;
2156 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2157 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2160 final boolean drawables = left != null || top != null || right != null || bottom != null;
2162 // Clearing drawables... can we free the data structure?
2164 if (dr.mDrawablePadding == 0) {
2167 // We need to retain the last set padding, so just clear
2168 // out all of the fields in the existing structure.
2169 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2170 if (dr.mShowing[i] != null) {
2171 dr.mShowing[i].setCallback(null);
2173 dr.mShowing[i] = null;
2175 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2176 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2177 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2178 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2183 mDrawables = dr = new Drawables(getContext());
2186 mDrawables.mOverride = false;
2188 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2189 dr.mShowing[Drawables.LEFT].setCallback(null);
2191 dr.mShowing[Drawables.LEFT] = left;
2193 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2194 dr.mShowing[Drawables.TOP].setCallback(null);
2196 dr.mShowing[Drawables.TOP] = top;
2198 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2199 dr.mShowing[Drawables.RIGHT].setCallback(null);
2201 dr.mShowing[Drawables.RIGHT] = right;
2203 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2204 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2206 dr.mShowing[Drawables.BOTTOM] = bottom;
2208 final Rect compoundRect = dr.mCompoundRect;
2211 state = getDrawableState();
2214 left.setState(state);
2215 left.copyBounds(compoundRect);
2216 left.setCallback(this);
2217 dr.mDrawableSizeLeft = compoundRect.width();
2218 dr.mDrawableHeightLeft = compoundRect.height();
2220 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2223 if (right != null) {
2224 right.setState(state);
2225 right.copyBounds(compoundRect);
2226 right.setCallback(this);
2227 dr.mDrawableSizeRight = compoundRect.width();
2228 dr.mDrawableHeightRight = compoundRect.height();
2230 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2234 top.setState(state);
2235 top.copyBounds(compoundRect);
2236 top.setCallback(this);
2237 dr.mDrawableSizeTop = compoundRect.height();
2238 dr.mDrawableWidthTop = compoundRect.width();
2240 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2243 if (bottom != null) {
2244 bottom.setState(state);
2245 bottom.copyBounds(compoundRect);
2246 bottom.setCallback(this);
2247 dr.mDrawableSizeBottom = compoundRect.height();
2248 dr.mDrawableWidthBottom = compoundRect.width();
2250 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2254 // Save initial left/right drawables
2256 dr.mDrawableLeftInitial = left;
2257 dr.mDrawableRightInitial = right;
2260 resetResolvedDrawables();
2262 applyCompoundDrawableTint();
2268 * Sets the Drawables (if any) to appear to the left of, above, to the
2269 * right of, and below the text. Use 0 if you do not want a Drawable there.
2270 * The Drawables' bounds will be set to their intrinsic bounds.
2272 * Calling this method will overwrite any Drawables previously set using
2273 * {@link #setCompoundDrawablesRelative} or related methods.
2275 * @param left Resource identifier of the left Drawable.
2276 * @param top Resource identifier of the top Drawable.
2277 * @param right Resource identifier of the right Drawable.
2278 * @param bottom Resource identifier of the bottom Drawable.
2280 * @attr ref android.R.styleable#TextView_drawableLeft
2281 * @attr ref android.R.styleable#TextView_drawableTop
2282 * @attr ref android.R.styleable#TextView_drawableRight
2283 * @attr ref android.R.styleable#TextView_drawableBottom
2285 @android.view.RemotableViewMethod
2286 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2287 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2288 final Context context = getContext();
2289 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2290 top != 0 ? context.getDrawable(top) : null,
2291 right != 0 ? context.getDrawable(right) : null,
2292 bottom != 0 ? context.getDrawable(bottom) : null);
2296 * Sets the Drawables (if any) to appear to the left of, above, to the
2297 * right of, and below the text. Use {@code null} if you do not want a
2298 * Drawable there. The Drawables' bounds will be set to their intrinsic
2301 * Calling this method will overwrite any Drawables previously set using
2302 * {@link #setCompoundDrawablesRelative} or related methods.
2304 * @attr ref android.R.styleable#TextView_drawableLeft
2305 * @attr ref android.R.styleable#TextView_drawableTop
2306 * @attr ref android.R.styleable#TextView_drawableRight
2307 * @attr ref android.R.styleable#TextView_drawableBottom
2309 @android.view.RemotableViewMethod
2310 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2311 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2314 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2316 if (right != null) {
2317 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2320 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2322 if (bottom != null) {
2323 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2325 setCompoundDrawables(left, top, right, bottom);
2329 * Sets the Drawables (if any) to appear to the start of, above, to the end
2330 * of, and below the text. Use {@code null} if you do not want a Drawable
2331 * there. The Drawables must already have had {@link Drawable#setBounds}
2334 * Calling this method will overwrite any Drawables previously set using
2335 * {@link #setCompoundDrawables} or related methods.
2337 * @attr ref android.R.styleable#TextView_drawableStart
2338 * @attr ref android.R.styleable#TextView_drawableTop
2339 * @attr ref android.R.styleable#TextView_drawableEnd
2340 * @attr ref android.R.styleable#TextView_drawableBottom
2342 @android.view.RemotableViewMethod
2343 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2344 @Nullable Drawable end, @Nullable Drawable bottom) {
2345 Drawables dr = mDrawables;
2347 // We're switching to relative, discard absolute.
2349 if (dr.mShowing[Drawables.LEFT] != null) {
2350 dr.mShowing[Drawables.LEFT].setCallback(null);
2352 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2353 if (dr.mShowing[Drawables.RIGHT] != null) {
2354 dr.mShowing[Drawables.RIGHT].setCallback(null);
2356 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2357 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2358 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2361 final boolean drawables = start != null || top != null
2362 || end != null || bottom != null;
2365 // Clearing drawables... can we free the data structure?
2367 if (dr.mDrawablePadding == 0) {
2370 // We need to retain the last set padding, so just clear
2371 // out all of the fields in the existing structure.
2372 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2373 dr.mDrawableStart = null;
2374 if (dr.mShowing[Drawables.TOP] != null) {
2375 dr.mShowing[Drawables.TOP].setCallback(null);
2377 dr.mShowing[Drawables.TOP] = null;
2378 if (dr.mDrawableEnd != null) {
2379 dr.mDrawableEnd.setCallback(null);
2381 dr.mDrawableEnd = null;
2382 if (dr.mShowing[Drawables.BOTTOM] != null) {
2383 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2385 dr.mShowing[Drawables.BOTTOM] = null;
2386 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2387 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2388 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2389 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2394 mDrawables = dr = new Drawables(getContext());
2397 mDrawables.mOverride = true;
2399 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2400 dr.mDrawableStart.setCallback(null);
2402 dr.mDrawableStart = start;
2404 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2405 dr.mShowing[Drawables.TOP].setCallback(null);
2407 dr.mShowing[Drawables.TOP] = top;
2409 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2410 dr.mDrawableEnd.setCallback(null);
2412 dr.mDrawableEnd = end;
2414 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2415 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2417 dr.mShowing[Drawables.BOTTOM] = bottom;
2419 final Rect compoundRect = dr.mCompoundRect;
2422 state = getDrawableState();
2424 if (start != null) {
2425 start.setState(state);
2426 start.copyBounds(compoundRect);
2427 start.setCallback(this);
2428 dr.mDrawableSizeStart = compoundRect.width();
2429 dr.mDrawableHeightStart = compoundRect.height();
2431 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2435 end.setState(state);
2436 end.copyBounds(compoundRect);
2437 end.setCallback(this);
2438 dr.mDrawableSizeEnd = compoundRect.width();
2439 dr.mDrawableHeightEnd = compoundRect.height();
2441 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2445 top.setState(state);
2446 top.copyBounds(compoundRect);
2447 top.setCallback(this);
2448 dr.mDrawableSizeTop = compoundRect.height();
2449 dr.mDrawableWidthTop = compoundRect.width();
2451 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2454 if (bottom != null) {
2455 bottom.setState(state);
2456 bottom.copyBounds(compoundRect);
2457 bottom.setCallback(this);
2458 dr.mDrawableSizeBottom = compoundRect.height();
2459 dr.mDrawableWidthBottom = compoundRect.width();
2461 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2465 resetResolvedDrawables();
2472 * Sets the Drawables (if any) to appear to the start of, above, to the end
2473 * of, and below the text. Use 0 if you do not want a Drawable there. The
2474 * Drawables' bounds will be set to their intrinsic bounds.
2476 * Calling this method will overwrite any Drawables previously set using
2477 * {@link #setCompoundDrawables} or related methods.
2479 * @param start Resource identifier of the start Drawable.
2480 * @param top Resource identifier of the top Drawable.
2481 * @param end Resource identifier of the end Drawable.
2482 * @param bottom Resource identifier of the bottom Drawable.
2484 * @attr ref android.R.styleable#TextView_drawableStart
2485 * @attr ref android.R.styleable#TextView_drawableTop
2486 * @attr ref android.R.styleable#TextView_drawableEnd
2487 * @attr ref android.R.styleable#TextView_drawableBottom
2489 @android.view.RemotableViewMethod
2490 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2491 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
2492 final Context context = getContext();
2493 setCompoundDrawablesRelativeWithIntrinsicBounds(
2494 start != 0 ? context.getDrawable(start) : null,
2495 top != 0 ? context.getDrawable(top) : null,
2496 end != 0 ? context.getDrawable(end) : null,
2497 bottom != 0 ? context.getDrawable(bottom) : null);
2501 * Sets the Drawables (if any) to appear to the start of, above, to the end
2502 * of, and below the text. Use {@code null} if you do not want a Drawable
2503 * there. The Drawables' bounds will be set to their intrinsic bounds.
2505 * Calling this method will overwrite any Drawables previously set using
2506 * {@link #setCompoundDrawables} or related methods.
2508 * @attr ref android.R.styleable#TextView_drawableStart
2509 * @attr ref android.R.styleable#TextView_drawableTop
2510 * @attr ref android.R.styleable#TextView_drawableEnd
2511 * @attr ref android.R.styleable#TextView_drawableBottom
2513 @android.view.RemotableViewMethod
2514 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2515 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2517 if (start != null) {
2518 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2521 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2524 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2526 if (bottom != null) {
2527 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2529 setCompoundDrawablesRelative(start, top, end, bottom);
2533 * Returns drawables for the left, top, right, and bottom borders.
2535 * @attr ref android.R.styleable#TextView_drawableLeft
2536 * @attr ref android.R.styleable#TextView_drawableTop
2537 * @attr ref android.R.styleable#TextView_drawableRight
2538 * @attr ref android.R.styleable#TextView_drawableBottom
2541 public Drawable[] getCompoundDrawables() {
2542 final Drawables dr = mDrawables;
2544 return dr.mShowing.clone();
2546 return new Drawable[] { null, null, null, null };
2551 * Returns drawables for the start, top, end, and bottom borders.
2553 * @attr ref android.R.styleable#TextView_drawableStart
2554 * @attr ref android.R.styleable#TextView_drawableTop
2555 * @attr ref android.R.styleable#TextView_drawableEnd
2556 * @attr ref android.R.styleable#TextView_drawableBottom
2559 public Drawable[] getCompoundDrawablesRelative() {
2560 final Drawables dr = mDrawables;
2562 return new Drawable[] {
2563 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2564 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
2567 return new Drawable[] { null, null, null, null };
2572 * Sets the size of the padding between the compound drawables and
2575 * @attr ref android.R.styleable#TextView_drawablePadding
2577 @android.view.RemotableViewMethod
2578 public void setCompoundDrawablePadding(int pad) {
2579 Drawables dr = mDrawables;
2582 dr.mDrawablePadding = pad;
2586 mDrawables = dr = new Drawables(getContext());
2588 dr.mDrawablePadding = pad;
2596 * Returns the padding between the compound drawables and the text.
2598 * @attr ref android.R.styleable#TextView_drawablePadding
2600 public int getCompoundDrawablePadding() {
2601 final Drawables dr = mDrawables;
2602 return dr != null ? dr.mDrawablePadding : 0;
2606 * Applies a tint to the compound drawables. Does not modify the
2607 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2609 * Subsequent calls to
2610 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2611 * and related methods will automatically mutate the drawables and apply
2612 * the specified tint and tint mode using
2613 * {@link Drawable#setTintList(ColorStateList)}.
2615 * @param tint the tint to apply, may be {@code null} to clear tint
2617 * @attr ref android.R.styleable#TextView_drawableTint
2618 * @see #getCompoundDrawableTintList()
2619 * @see Drawable#setTintList(ColorStateList)
2621 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2622 if (mDrawables == null) {
2623 mDrawables = new Drawables(getContext());
2625 mDrawables.mTintList = tint;
2626 mDrawables.mHasTint = true;
2628 applyCompoundDrawableTint();
2632 * @return the tint applied to the compound drawables
2633 * @attr ref android.R.styleable#TextView_drawableTint
2634 * @see #setCompoundDrawableTintList(ColorStateList)
2636 public ColorStateList getCompoundDrawableTintList() {
2637 return mDrawables != null ? mDrawables.mTintList : null;
2641 * Specifies the blending mode used to apply the tint specified by
2642 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2643 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2645 * @param tintMode the blending mode used to apply the tint, may be
2646 * {@code null} to clear tint
2647 * @attr ref android.R.styleable#TextView_drawableTintMode
2648 * @see #setCompoundDrawableTintList(ColorStateList)
2649 * @see Drawable#setTintMode(PorterDuff.Mode)
2651 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2652 if (mDrawables == null) {
2653 mDrawables = new Drawables(getContext());
2655 mDrawables.mTintMode = tintMode;
2656 mDrawables.mHasTintMode = true;
2658 applyCompoundDrawableTint();
2662 * Returns the blending mode used to apply the tint to the compound
2663 * drawables, if specified.
2665 * @return the blending mode used to apply the tint to the compound
2667 * @attr ref android.R.styleable#TextView_drawableTintMode
2668 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2670 public PorterDuff.Mode getCompoundDrawableTintMode() {
2671 return mDrawables != null ? mDrawables.mTintMode : null;
2674 private void applyCompoundDrawableTint() {
2675 if (mDrawables == null) {
2679 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2680 final ColorStateList tintList = mDrawables.mTintList;
2681 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2682 final boolean hasTint = mDrawables.mHasTint;
2683 final boolean hasTintMode = mDrawables.mHasTintMode;
2684 final int[] state = getDrawableState();
2686 for (Drawable dr : mDrawables.mShowing) {
2691 if (dr == mDrawables.mDrawableError) {
2692 // From a developer's perspective, the error drawable isn't
2693 // a compound drawable. Don't apply the generic compound
2694 // drawable tint to it.
2701 dr.setTintList(tintList);
2705 dr.setTintMode(tintMode);
2708 // The drawable (or one of its children) may not have been
2709 // stateful before applying the tint, so let's try again.
2710 if (dr.isStateful()) {
2718 public void setPadding(int left, int top, int right, int bottom) {
2719 if (left != mPaddingLeft ||
2720 right != mPaddingRight ||
2721 top != mPaddingTop ||
2722 bottom != mPaddingBottom) {
2726 // the super call will requestLayout()
2727 super.setPadding(left, top, right, bottom);
2732 public void setPaddingRelative(int start, int top, int end, int bottom) {
2733 if (start != getPaddingStart() ||
2734 end != getPaddingEnd() ||
2735 top != mPaddingTop ||
2736 bottom != mPaddingBottom) {
2740 // the super call will requestLayout()
2741 super.setPaddingRelative(start, top, end, bottom);
2746 * Gets the autolink mask of the text. See {@link
2747 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2750 * @attr ref android.R.styleable#TextView_autoLink
2752 public final int getAutoLinkMask() {
2753 return mAutoLinkMask;
2757 * Sets the text appearance from the specified style resource.
2759 * Use a framework-defined {@code TextAppearance} style like
2760 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2761 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2762 * set of attributes that can be used in a custom style.
2764 * @param resId the resource identifier of the style to apply
2765 * @attr ref android.R.styleable#TextView_textAppearance
2767 @SuppressWarnings("deprecation")
2768 public void setTextAppearance(@StyleRes int resId) {
2769 setTextAppearance(mContext, resId);
2773 * Sets the text color, size, style, hint color, and highlight color
2774 * from the specified TextAppearance resource.
2776 * @deprecated Use {@link #setTextAppearance(int)} instead.
2779 public void setTextAppearance(Context context, @StyleRes int resId) {
2780 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
2782 final int textColorHighlight = ta.getColor(
2783 R.styleable.TextAppearance_textColorHighlight, 0);
2784 if (textColorHighlight != 0) {
2785 setHighlightColor(textColorHighlight);
2788 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2789 if (textColor != null) {
2790 setTextColor(textColor);
2793 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2794 if (textSize != 0) {
2795 setRawTextSize(textSize);
2798 final ColorStateList textColorHint = ta.getColorStateList(
2799 R.styleable.TextAppearance_textColorHint);
2800 if (textColorHint != null) {
2801 setHintTextColor(textColorHint);
2804 final ColorStateList textColorLink = ta.getColorStateList(
2805 R.styleable.TextAppearance_textColorLink);
2806 if (textColorLink != null) {
2807 setLinkTextColor(textColorLink);
2810 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2811 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2812 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2813 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
2815 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2816 if (shadowColor != 0) {
2817 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2818 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2819 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2820 setShadowLayer(r, dx, dy, shadowColor);
2823 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
2824 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2827 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2828 setElegantTextHeight(ta.getBoolean(
2829 R.styleable.TextAppearance_elegantTextHeight, false));
2832 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2833 setLetterSpacing(ta.getFloat(
2834 R.styleable.TextAppearance_letterSpacing, 0));
2837 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2838 setFontFeatureSettings(ta.getString(
2839 R.styleable.TextAppearance_fontFeatureSettings));
2846 * Get the default primary {@link Locale} of the text in this TextView. This will always be
2847 * the first member of {@link #getTextLocales()}.
2848 * @return the default primary {@link Locale} of the text in this TextView.
2851 public Locale getTextLocale() {
2852 return mTextPaint.getTextLocale();
2856 * Get the default {@link LocaleList} of the text in this TextView.
2857 * @return the default {@link LocaleList} of the text in this TextView.
2859 @NonNull @Size(min=1)
2860 public LocaleList getTextLocales() {
2861 return mTextPaint.getTextLocales();
2865 * Set the default {@link LocaleList} of the text in this TextView to a one-member list
2866 * containing just the given value.
2868 * @param locale the {@link Locale} for drawing text, must not be null.
2870 * @see #setTextLocales
2872 public void setTextLocale(@NonNull Locale locale) {
2873 mLocalesChanged = true;
2874 mTextPaint.setTextLocale(locale);
2878 * Set the default {@link LocaleList} of the text in this TextView to the given value.
2880 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
2881 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
2882 * other aspects of text display, including line breaking.
2884 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
2886 * @see Paint#setTextLocales
2888 public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
2889 mLocalesChanged = true;
2890 mTextPaint.setTextLocales(locales);
2894 protected void onConfigurationChanged(Configuration newConfig) {
2895 super.onConfigurationChanged(newConfig);
2896 if (!mLocalesChanged) {
2897 mTextPaint.setTextLocales(LocaleList.getDefault());
2902 * @return the size (in pixels) of the default text size in this TextView.
2904 @ViewDebug.ExportedProperty(category = "text")
2905 public float getTextSize() {
2906 return mTextPaint.getTextSize();
2910 * @return the size (in scaled pixels) of thee default text size in this TextView.
2913 @ViewDebug.ExportedProperty(category = "text")
2914 public float getScaledTextSize() {
2915 return mTextPaint.getTextSize() / mTextPaint.density;
2919 @ViewDebug.ExportedProperty(category = "text", mapping = {
2920 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2921 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2922 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2923 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2925 public int getTypefaceStyle() {
2926 Typeface typeface = mTextPaint.getTypeface();
2927 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
2931 * Set the default text size to the given value, interpreted as "scaled
2932 * pixel" units. This size is adjusted based on the current density and
2933 * user font size preference.
2935 * @param size The scaled pixel size.
2937 * @attr ref android.R.styleable#TextView_textSize
2939 @android.view.RemotableViewMethod
2940 public void setTextSize(float size) {
2941 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2945 * Set the default text size to a given unit and value. See {@link
2946 * TypedValue} for the possible dimension units.
2948 * @param unit The desired dimension unit.
2949 * @param size The desired size in the given units.
2951 * @attr ref android.R.styleable#TextView_textSize
2953 public void setTextSize(int unit, float size) {
2954 Context c = getContext();
2958 r = Resources.getSystem();
2960 r = c.getResources();
2962 setRawTextSize(TypedValue.applyDimension(
2963 unit, size, r.getDisplayMetrics()));
2966 private void setRawTextSize(float size) {
2967 if (size != mTextPaint.getTextSize()) {
2968 mTextPaint.setTextSize(size);
2970 if (mLayout != null) {
2979 * @return the extent by which text is currently being stretched
2980 * horizontally. This will usually be 1.
2982 public float getTextScaleX() {
2983 return mTextPaint.getTextScaleX();
2987 * Sets the extent by which text should be stretched horizontally.
2989 * @attr ref android.R.styleable#TextView_textScaleX
2991 @android.view.RemotableViewMethod
2992 public void setTextScaleX(float size) {
2993 if (size != mTextPaint.getTextScaleX()) {
2994 mUserSetTextScaleX = true;
2995 mTextPaint.setTextScaleX(size);
2997 if (mLayout != null) {
3006 * Sets the typeface and style in which the text should be displayed.
3007 * Note that not all Typeface families actually have bold and italic
3008 * variants, so you may need to use
3009 * {@link #setTypeface(Typeface, int)} to get the appearance
3010 * that you actually want.
3012 * @see #getTypeface()
3014 * @attr ref android.R.styleable#TextView_fontFamily
3015 * @attr ref android.R.styleable#TextView_typeface
3016 * @attr ref android.R.styleable#TextView_textStyle
3018 public void setTypeface(Typeface tf) {
3019 if (mTextPaint.getTypeface() != tf) {
3020 mTextPaint.setTypeface(tf);
3022 if (mLayout != null) {
3031 * @return the current typeface and style in which the text is being
3034 * @see #setTypeface(Typeface)
3036 * @attr ref android.R.styleable#TextView_fontFamily
3037 * @attr ref android.R.styleable#TextView_typeface
3038 * @attr ref android.R.styleable#TextView_textStyle
3040 public Typeface getTypeface() {
3041 return mTextPaint.getTypeface();
3045 * Set the TextView's elegant height metrics flag. This setting selects font
3046 * variants that have not been compacted to fit Latin-based vertical
3047 * metrics, and also increases top and bottom bounds to provide more space.
3049 * @param elegant set the paint's elegant metrics flag.
3051 * @attr ref android.R.styleable#TextView_elegantTextHeight
3053 public void setElegantTextHeight(boolean elegant) {
3054 mTextPaint.setElegantTextHeight(elegant);
3058 * @return the extent by which text is currently being letter-spaced.
3059 * This will normally be 0.
3061 * @see #setLetterSpacing(float)
3062 * @see Paint#setLetterSpacing
3064 public float getLetterSpacing() {
3065 return mTextPaint.getLetterSpacing();
3069 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3070 * for slight expansion will be around 0.05. Negative values tighten text.
3072 * @see #getLetterSpacing()
3073 * @see Paint#getLetterSpacing
3075 * @attr ref android.R.styleable#TextView_letterSpacing
3077 @android.view.RemotableViewMethod
3078 public void setLetterSpacing(float letterSpacing) {
3079 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3080 mTextPaint.setLetterSpacing(letterSpacing);
3082 if (mLayout != null) {
3091 * @return the currently set font feature settings. Default is null.
3093 * @see #setFontFeatureSettings(String)
3094 * @see Paint#setFontFeatureSettings
3097 public String getFontFeatureSettings() {
3098 return mTextPaint.getFontFeatureSettings();
3102 * Sets the break strategy for breaking paragraphs into lines. The default value for
3103 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3104 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3105 * text "dancing" when being edited.
3107 * @attr ref android.R.styleable#TextView_breakStrategy
3108 * @see #getBreakStrategy()
3110 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3111 mBreakStrategy = breakStrategy;
3112 if (mLayout != null) {
3120 * @return the currently set break strategy.
3122 * @attr ref android.R.styleable#TextView_breakStrategy
3123 * @see #setBreakStrategy(int)
3125 @Layout.BreakStrategy
3126 public int getBreakStrategy() {
3127 return mBreakStrategy;
3131 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3132 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3134 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3135 * @see #getHyphenationFrequency()
3137 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3138 mHyphenationFrequency = hyphenationFrequency;
3139 if (mLayout != null) {
3147 * @return the currently set hyphenation frequency.
3149 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3150 * @see #setHyphenationFrequency(int)
3152 @Layout.HyphenationFrequency
3153 public int getHyphenationFrequency() {
3154 return mHyphenationFrequency;
3158 * Sets font feature settings. The format is the same as the CSS
3159 * font-feature-settings attribute:
3160 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3162 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3163 * @see #getFontFeatureSettings()
3164 * @see Paint#getFontFeatureSettings
3166 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3168 @android.view.RemotableViewMethod
3169 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3170 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3171 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3173 if (mLayout != null) {
3183 * Sets the text color for all the states (normal, selected,
3184 * focused) to be this color.
3186 * @see #setTextColor(ColorStateList)
3187 * @see #getTextColors()
3189 * @attr ref android.R.styleable#TextView_textColor
3191 @android.view.RemotableViewMethod
3192 public void setTextColor(@ColorInt int color) {
3193 mTextColor = ColorStateList.valueOf(color);
3198 * Sets the text color.
3200 * @see #setTextColor(int)
3201 * @see #getTextColors()
3202 * @see #setHintTextColor(ColorStateList)
3203 * @see #setLinkTextColor(ColorStateList)
3205 * @attr ref android.R.styleable#TextView_textColor
3207 public void setTextColor(ColorStateList colors) {
3208 if (colors == null) {
3209 throw new NullPointerException();
3212 mTextColor = colors;
3217 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3219 * @see #setTextColor(ColorStateList)
3220 * @see #setTextColor(int)
3222 * @attr ref android.R.styleable#TextView_textColor
3224 public final ColorStateList getTextColors() {
3229 * <p>Return the current color selected for normal text.</p>
3231 * @return Returns the current text color.
3234 public final int getCurrentTextColor() {
3235 return mCurTextColor;
3239 * Sets the color used to display the selection highlight.
3241 * @attr ref android.R.styleable#TextView_textColorHighlight
3243 @android.view.RemotableViewMethod
3244 public void setHighlightColor(@ColorInt int color) {
3245 if (mHighlightColor != color) {
3246 mHighlightColor = color;
3252 * @return the color used to display the selection highlight
3254 * @see #setHighlightColor(int)
3256 * @attr ref android.R.styleable#TextView_textColorHighlight
3259 public int getHighlightColor() {
3260 return mHighlightColor;
3264 * Sets whether the soft input method will be made visible when this
3265 * TextView gets focused. The default is true.
3267 @android.view.RemotableViewMethod
3268 public final void setShowSoftInputOnFocus(boolean show) {
3269 createEditorIfNeeded();
3270 mEditor.mShowSoftInputOnFocus = show;
3274 * Returns whether the soft input method will be made visible when this
3275 * TextView gets focused. The default is true.
3277 public final boolean getShowSoftInputOnFocus() {
3278 // When there is no Editor, return default true value
3279 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3283 * Gives the text a shadow of the specified blur radius and color, the specified
3284 * distance from its drawn position.
3286 * The text shadow produced does not interact with the properties on view
3287 * that are responsible for real time shadows,
3288 * {@link View#getElevation() elevation} and
3289 * {@link View#getTranslationZ() translationZ}.
3291 * @see Paint#setShadowLayer(float, float, float, int)
3293 * @attr ref android.R.styleable#TextView_shadowColor
3294 * @attr ref android.R.styleable#TextView_shadowDx
3295 * @attr ref android.R.styleable#TextView_shadowDy
3296 * @attr ref android.R.styleable#TextView_shadowRadius
3298 public void setShadowLayer(float radius, float dx, float dy, int color) {
3299 mTextPaint.setShadowLayer(radius, dx, dy, color);
3301 mShadowRadius = radius;
3304 mShadowColor = color;
3306 // Will change text clip region
3307 if (mEditor != null) mEditor.invalidateTextDisplayList();
3312 * Gets the radius of the shadow layer.
3314 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3316 * @see #setShadowLayer(float, float, float, int)
3318 * @attr ref android.R.styleable#TextView_shadowRadius
3320 public float getShadowRadius() {
3321 return mShadowRadius;
3325 * @return the horizontal offset of the shadow layer
3327 * @see #setShadowLayer(float, float, float, int)
3329 * @attr ref android.R.styleable#TextView_shadowDx
3331 public float getShadowDx() {
3336 * @return the vertical offset of the shadow layer
3338 * @see #setShadowLayer(float, float, float, int)
3340 * @attr ref android.R.styleable#TextView_shadowDy
3342 public float getShadowDy() {
3347 * @return the color of the shadow layer
3349 * @see #setShadowLayer(float, float, float, int)
3351 * @attr ref android.R.styleable#TextView_shadowColor
3354 public int getShadowColor() {
3355 return mShadowColor;
3359 * @return the base paint used for the text. Please use this only to
3360 * consult the Paint's properties and not to change them.
3362 public TextPaint getPaint() {
3367 * Sets the autolink mask of the text. See {@link
3368 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3371 * @attr ref android.R.styleable#TextView_autoLink
3373 @android.view.RemotableViewMethod
3374 public final void setAutoLinkMask(int mask) {
3375 mAutoLinkMask = mask;
3379 * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
3380 * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
3381 * following is true:
3383 * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
3384 * {@link #setText} or {@link #append}.
3385 * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
3388 * <p>This function does not have an immediate effect, movement method will be set only after a
3389 * call to {@link #setText} or {@link #append}. The default is true.</p>
3391 * @attr ref android.R.styleable#TextView_linksClickable
3393 @android.view.RemotableViewMethod
3394 public final void setLinksClickable(boolean whether) {
3395 mLinksClickable = whether;
3399 * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
3400 * after {@link #setText} or {@link #append} is called.
3402 * See {@link #setLinksClickable} for details.
3404 * <p>The default is true.</p>
3406 * @see #setLinksClickable
3408 * @attr ref android.R.styleable#TextView_linksClickable
3410 public final boolean getLinksClickable() {
3411 return mLinksClickable;
3415 * Returns the list of URLSpans attached to the text
3416 * (by {@link Linkify} or otherwise) if any. You can call
3417 * {@link URLSpan#getURL} on them to find where they link to
3418 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3419 * to find the region of the text they are attached to.
3421 public URLSpan[] getUrls() {
3422 if (mText instanceof Spanned) {
3423 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3425 return new URLSpan[0];
3430 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3433 * @see #setHintTextColor(ColorStateList)
3434 * @see #getHintTextColors()
3435 * @see #setTextColor(int)
3437 * @attr ref android.R.styleable#TextView_textColorHint
3439 @android.view.RemotableViewMethod
3440 public final void setHintTextColor(@ColorInt int color) {
3441 mHintTextColor = ColorStateList.valueOf(color);
3446 * Sets the color of the hint text.
3448 * @see #getHintTextColors()
3449 * @see #setHintTextColor(int)
3450 * @see #setTextColor(ColorStateList)
3451 * @see #setLinkTextColor(ColorStateList)
3453 * @attr ref android.R.styleable#TextView_textColorHint
3455 public final void setHintTextColor(ColorStateList colors) {
3456 mHintTextColor = colors;
3461 * @return the color of the hint text, for the different states of this TextView.
3463 * @see #setHintTextColor(ColorStateList)
3464 * @see #setHintTextColor(int)
3465 * @see #setTextColor(ColorStateList)
3466 * @see #setLinkTextColor(ColorStateList)
3468 * @attr ref android.R.styleable#TextView_textColorHint
3470 public final ColorStateList getHintTextColors() {
3471 return mHintTextColor;
3475 * <p>Return the current color selected to paint the hint text.</p>
3477 * @return Returns the current hint text color.
3480 public final int getCurrentHintTextColor() {
3481 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3485 * Sets the color of links in the text.
3487 * @see #setLinkTextColor(ColorStateList)
3488 * @see #getLinkTextColors()
3490 * @attr ref android.R.styleable#TextView_textColorLink
3492 @android.view.RemotableViewMethod
3493 public final void setLinkTextColor(@ColorInt int color) {
3494 mLinkTextColor = ColorStateList.valueOf(color);
3499 * Sets the color of links in the text.
3501 * @see #setLinkTextColor(int)
3502 * @see #getLinkTextColors()
3503 * @see #setTextColor(ColorStateList)
3504 * @see #setHintTextColor(ColorStateList)
3506 * @attr ref android.R.styleable#TextView_textColorLink
3508 public final void setLinkTextColor(ColorStateList colors) {
3509 mLinkTextColor = colors;
3514 * @return the list of colors used to paint the links in the text, for the different states of
3517 * @see #setLinkTextColor(ColorStateList)
3518 * @see #setLinkTextColor(int)
3520 * @attr ref android.R.styleable#TextView_textColorLink
3522 public final ColorStateList getLinkTextColors() {
3523 return mLinkTextColor;
3527 * Sets the horizontal alignment of the text and the
3528 * vertical gravity that will be used when there is extra space
3529 * in the TextView beyond what is required for the text itself.
3531 * @see android.view.Gravity
3532 * @attr ref android.R.styleable#TextView_gravity
3534 public void setGravity(int gravity) {
3535 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3536 gravity |= Gravity.START;
3538 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3539 gravity |= Gravity.TOP;
3542 boolean newLayout = false;
3544 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3545 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3549 if (gravity != mGravity) {
3555 if (mLayout != null && newLayout) {
3556 // XXX this is heavy-handed because no actual content changes.
3557 int want = mLayout.getWidth();
3558 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3560 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3561 mRight - mLeft - getCompoundPaddingLeft() -
3562 getCompoundPaddingRight(), true);
3567 * Returns the horizontal and vertical alignment of this TextView.
3569 * @see android.view.Gravity
3570 * @attr ref android.R.styleable#TextView_gravity
3572 public int getGravity() {
3577 * @return the flags on the Paint being used to display the text.
3578 * @see Paint#getFlags
3580 public int getPaintFlags() {
3581 return mTextPaint.getFlags();
3585 * Sets flags on the Paint being used to display the text and
3586 * reflows the text if they are different from the old flags.
3587 * @see Paint#setFlags
3589 @android.view.RemotableViewMethod
3590 public void setPaintFlags(int flags) {
3591 if (mTextPaint.getFlags() != flags) {
3592 mTextPaint.setFlags(flags);
3594 if (mLayout != null) {
3603 * Sets whether the text should be allowed to be wider than the
3604 * View is. If false, it will be wrapped to the width of the View.
3606 * @attr ref android.R.styleable#TextView_scrollHorizontally
3608 public void setHorizontallyScrolling(boolean whether) {
3609 if (mHorizontallyScrolling != whether) {
3610 mHorizontallyScrolling = whether;
3612 if (mLayout != null) {
3621 * Returns whether the text is allowed to be wider than the View is.
3622 * If false, the text will be wrapped to the width of the View.
3624 * @attr ref android.R.styleable#TextView_scrollHorizontally
3627 public boolean getHorizontallyScrolling() {
3628 return mHorizontallyScrolling;
3632 * Makes the TextView at least this many lines tall.
3634 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3635 * set this value to 1.
3637 * @see #getMinLines()
3639 * @attr ref android.R.styleable#TextView_minLines
3641 @android.view.RemotableViewMethod
3642 public void setMinLines(int minlines) {
3643 mMinimum = minlines;
3651 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3652 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3654 * @see #setMinLines(int)
3656 * @attr ref android.R.styleable#TextView_minLines
3658 public int getMinLines() {
3659 return mMinMode == LINES ? mMinimum : -1;
3663 * Makes the TextView at least this many pixels tall.
3665 * Setting this value overrides any other (minimum) number of lines setting.
3667 * @attr ref android.R.styleable#TextView_minHeight
3669 @android.view.RemotableViewMethod
3670 public void setMinHeight(int minHeight) {
3671 mMinimum = minHeight;
3679 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3680 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3682 * @see #setMinHeight(int)
3684 * @attr ref android.R.styleable#TextView_minHeight
3686 public int getMinHeight() {
3687 return mMinMode == PIXELS ? mMinimum : -1;
3691 * Makes the TextView at most this many lines tall.
3693 * Setting this value overrides any other (maximum) height setting.
3695 * @attr ref android.R.styleable#TextView_maxLines
3697 @android.view.RemotableViewMethod
3698 public void setMaxLines(int maxlines) {
3699 mMaximum = maxlines;
3707 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3708 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3710 * @see #setMaxLines(int)
3712 * @attr ref android.R.styleable#TextView_maxLines
3714 public int getMaxLines() {
3715 return mMaxMode == LINES ? mMaximum : -1;
3719 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3720 * {@link #setMaxLines(int)} method.
3722 * Setting this value overrides any other (maximum) number of lines setting.
3724 * @attr ref android.R.styleable#TextView_maxHeight
3726 @android.view.RemotableViewMethod
3727 public void setMaxHeight(int maxHeight) {
3728 mMaximum = maxHeight;
3736 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3737 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3739 * @see #setMaxHeight(int)
3741 * @attr ref android.R.styleable#TextView_maxHeight
3743 public int getMaxHeight() {
3744 return mMaxMode == PIXELS ? mMaximum : -1;
3748 * Makes the TextView exactly this many lines tall.
3750 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3751 * height setting. A single line TextView will set this value to 1.
3753 * @attr ref android.R.styleable#TextView_lines
3755 @android.view.RemotableViewMethod
3756 public void setLines(int lines) {
3757 mMaximum = mMinimum = lines;
3758 mMaxMode = mMinMode = LINES;
3765 * Makes the TextView exactly this many pixels tall.
3766 * You could do the same thing by specifying this number in the
3769 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3772 * @attr ref android.R.styleable#TextView_height
3774 @android.view.RemotableViewMethod
3775 public void setHeight(int pixels) {
3776 mMaximum = mMinimum = pixels;
3777 mMaxMode = mMinMode = PIXELS;
3784 * Makes the TextView at least this many ems wide
3786 * @attr ref android.R.styleable#TextView_minEms
3788 @android.view.RemotableViewMethod
3789 public void setMinEms(int minems) {
3791 mMinWidthMode = EMS;
3798 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3799 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3801 * @see #setMinEms(int)
3804 * @attr ref android.R.styleable#TextView_minEms
3806 public int getMinEms() {
3807 return mMinWidthMode == EMS ? mMinWidth : -1;
3811 * Makes the TextView at least this many pixels wide
3813 * @attr ref android.R.styleable#TextView_minWidth
3815 @android.view.RemotableViewMethod
3816 public void setMinWidth(int minpixels) {
3817 mMinWidth = minpixels;
3818 mMinWidthMode = PIXELS;
3825 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3826 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3828 * @see #setMinWidth(int)
3829 * @see #setWidth(int)
3831 * @attr ref android.R.styleable#TextView_minWidth
3833 public int getMinWidth() {
3834 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3838 * Makes the TextView at most this many ems wide
3840 * @attr ref android.R.styleable#TextView_maxEms
3842 @android.view.RemotableViewMethod
3843 public void setMaxEms(int maxems) {
3845 mMaxWidthMode = EMS;
3852 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3853 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3855 * @see #setMaxEms(int)
3858 * @attr ref android.R.styleable#TextView_maxEms
3860 public int getMaxEms() {
3861 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3865 * Makes the TextView at most this many pixels wide
3867 * @attr ref android.R.styleable#TextView_maxWidth
3869 @android.view.RemotableViewMethod
3870 public void setMaxWidth(int maxpixels) {
3871 mMaxWidth = maxpixels;
3872 mMaxWidthMode = PIXELS;
3879 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3880 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3882 * @see #setMaxWidth(int)
3883 * @see #setWidth(int)
3885 * @attr ref android.R.styleable#TextView_maxWidth
3887 public int getMaxWidth() {
3888 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3892 * Makes the TextView exactly this many ems wide
3894 * @see #setMaxEms(int)
3895 * @see #setMinEms(int)
3899 * @attr ref android.R.styleable#TextView_ems
3901 @android.view.RemotableViewMethod
3902 public void setEms(int ems) {
3903 mMaxWidth = mMinWidth = ems;
3904 mMaxWidthMode = mMinWidthMode = EMS;
3911 * Makes the TextView exactly this many pixels wide.
3912 * You could do the same thing by specifying this number in the
3915 * @see #setMaxWidth(int)
3916 * @see #setMinWidth(int)
3917 * @see #getMinWidth()
3918 * @see #getMaxWidth()
3920 * @attr ref android.R.styleable#TextView_width
3922 @android.view.RemotableViewMethod
3923 public void setWidth(int pixels) {
3924 mMaxWidth = mMinWidth = pixels;
3925 mMaxWidthMode = mMinWidthMode = PIXELS;
3932 * Sets line spacing for this TextView. Each line will have its height
3933 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3935 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3936 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3938 public void setLineSpacing(float add, float mult) {
3939 if (mSpacingAdd != add || mSpacingMult != mult) {
3941 mSpacingMult = mult;
3943 if (mLayout != null) {
3952 * Gets the line spacing multiplier
3954 * @return the value by which each line's height is multiplied to get its actual height.
3956 * @see #setLineSpacing(float, float)
3957 * @see #getLineSpacingExtra()
3959 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3961 public float getLineSpacingMultiplier() {
3962 return mSpacingMult;
3966 * Gets the line spacing extra space
3968 * @return the extra space that is added to the height of each lines of this TextView.
3970 * @see #setLineSpacing(float, float)
3971 * @see #getLineSpacingMultiplier()
3973 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3975 public float getLineSpacingExtra() {
3980 * Convenience method: Append the specified text to the TextView's
3981 * display buffer, upgrading it to BufferType.EDITABLE if it was
3982 * not already editable.
3984 public final void append(CharSequence text) {
3985 append(text, 0, text.length());
3989 * Convenience method: Append the specified text slice to the TextView's
3990 * display buffer, upgrading it to BufferType.EDITABLE if it was
3991 * not already editable.
3993 public void append(CharSequence text, int start, int end) {
3994 if (!(mText instanceof Editable)) {
3995 setText(mText, BufferType.EDITABLE);
3998 ((Editable) mText).append(text, start, end);
4000 boolean hasClickableSpans = false;
4001 if (mAutoLinkMask != 0) {
4002 hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4003 } else if (mLinksClickable && text instanceof Spanned) {
4004 ClickableSpan[] clickableSpans =
4005 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4006 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4009 // Do not change the movement method for text that supports text selection as it
4010 // would prevent an arbitrary cursor displacement.
4011 if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
4012 setMovementMethod(LinkMovementMethod.getInstance());
4016 private void updateTextColors() {
4017 boolean inval = false;
4018 int color = mTextColor.getColorForState(getDrawableState(), 0);
4019 if (color != mCurTextColor) {
4020 mCurTextColor = color;
4023 if (mLinkTextColor != null) {
4024 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4025 if (color != mTextPaint.linkColor) {
4026 mTextPaint.linkColor = color;
4030 if (mHintTextColor != null) {
4031 color = mHintTextColor.getColorForState(getDrawableState(), 0);
4032 if (color != mCurHintTextColor) {
4033 mCurHintTextColor = color;
4034 if (mText.length() == 0) {
4040 // Text needs to be redrawn with the new color
4041 if (mEditor != null) mEditor.invalidateTextDisplayList();
4047 protected void drawableStateChanged() {
4048 super.drawableStateChanged();
4050 if (mTextColor != null && mTextColor.isStateful()
4051 || (mHintTextColor != null && mHintTextColor.isStateful())
4052 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4056 if (mDrawables != null) {
4057 final int[] state = getDrawableState();
4058 for (Drawable dr : mDrawables.mShowing) {
4059 if (dr != null && dr.isStateful() && dr.setState(state)) {
4060 invalidateDrawable(dr);
4067 public void drawableHotspotChanged(float x, float y) {
4068 super.drawableHotspotChanged(x, y);
4070 if (mDrawables != null) {
4071 for (Drawable dr : mDrawables.mShowing) {
4073 dr.setHotspot(x, y);
4080 public Parcelable onSaveInstanceState() {
4081 Parcelable superState = super.onSaveInstanceState();
4083 // Save state if we are forced to
4084 boolean save = mFreezesText;
4088 if (mText != null) {
4089 start = getSelectionStart();
4090 end = getSelectionEnd();
4091 if (start >= 0 || end >= 0) {
4092 // Or save state if there is a selection
4098 SavedState ss = new SavedState(superState);
4099 // XXX Should also save the current scroll position!
4100 ss.selStart = start;
4103 if (mText instanceof Spanned) {
4104 Spannable sp = new SpannableStringBuilder(mText);
4106 if (mEditor != null) {
4107 removeMisspelledSpans(sp);
4108 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4113 ss.text = mText.toString();
4116 if (isFocused() && start >= 0 && end >= 0) {
4117 ss.frozenWithFocus = true;
4120 ss.error = getError();
4122 if (mEditor != null) {
4123 ss.editorState = mEditor.saveInstanceState();
4131 void removeMisspelledSpans(Spannable spannable) {
4132 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4133 SuggestionSpan.class);
4134 for (int i = 0; i < suggestionSpans.length; i++) {
4135 int flags = suggestionSpans[i].getFlags();
4136 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4137 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4138 spannable.removeSpan(suggestionSpans[i]);
4144 public void onRestoreInstanceState(Parcelable state) {
4145 if (!(state instanceof SavedState)) {
4146 super.onRestoreInstanceState(state);
4150 SavedState ss = (SavedState)state;
4151 super.onRestoreInstanceState(ss.getSuperState());
4153 // XXX restore buffer type too, as well as lots of other stuff
4154 if (ss.text != null) {
4158 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4159 if (mText instanceof Spannable) {
4160 int len = mText.length();
4162 if (ss.selStart > len || ss.selEnd > len) {
4163 String restored = "";
4165 if (ss.text != null) {
4166 restored = "(restored) ";
4169 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4170 "/" + ss.selEnd + " out of range for " + restored +
4173 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4175 if (ss.frozenWithFocus) {
4176 createEditorIfNeeded();
4177 mEditor.mFrozenWithFocus = true;
4183 if (ss.error != null) {
4184 final CharSequence error = ss.error;
4185 // Display the error later, after the first layout pass
4186 post(new Runnable() {
4188 if (mEditor == null || !mEditor.mErrorWasChanged) {
4195 if (ss.editorState != null) {
4196 createEditorIfNeeded();
4197 mEditor.restoreInstanceState(ss.editorState);
4202 * Control whether this text view saves its entire text contents when
4203 * freezing to an icicle, in addition to dynamic state such as cursor
4204 * position. By default this is false, not saving the text. Set to true
4205 * if the text in the text view is not being saved somewhere else in
4206 * persistent storage (such as in a content provider) so that if the
4207 * view is later thawed the user will not lose their data.
4209 * @param freezesText Controls whether a frozen icicle should include the
4210 * entire text data: true to include it, false to not.
4212 * @attr ref android.R.styleable#TextView_freezesText
4214 @android.view.RemotableViewMethod
4215 public void setFreezesText(boolean freezesText) {
4216 mFreezesText = freezesText;
4220 * Return whether this text view is including its entire text contents
4221 * in frozen icicles.
4223 * @return Returns true if text is included, false if it isn't.
4225 * @see #setFreezesText
4227 public boolean getFreezesText() {
4228 return mFreezesText;
4231 ///////////////////////////////////////////////////////////////////////////
4234 * Sets the Factory used to create new Editables.
4236 public final void setEditableFactory(Editable.Factory factory) {
4237 mEditableFactory = factory;
4242 * Sets the Factory used to create new Spannables.
4244 public final void setSpannableFactory(Spannable.Factory factory) {
4245 mSpannableFactory = factory;
4250 * Sets the string value of the TextView. TextView <em>does not</em> accept
4251 * HTML-like formatting, which you can do with text strings in XML resource files.
4252 * To style your strings, attach android.text.style.* objects to a
4253 * {@link android.text.SpannableString SpannableString}, or see the
4254 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4255 * Available Resource Types</a> documentation for an example of setting
4256 * formatted text in the XML resource file.
4258 * @attr ref android.R.styleable#TextView_text
4260 @android.view.RemotableViewMethod
4261 public final void setText(CharSequence text) {
4262 setText(text, mBufferType);
4266 * Like {@link #setText(CharSequence)},
4267 * except that the cursor position (if any) is retained in the new text.
4269 * @param text The new text to place in the text view.
4271 * @see #setText(CharSequence)
4273 @android.view.RemotableViewMethod
4274 public final void setTextKeepState(CharSequence text) {
4275 setTextKeepState(text, mBufferType);
4279 * Sets the text that this TextView is to display (see
4280 * {@link #setText(CharSequence)}) and also sets whether it is stored
4281 * in a styleable/spannable buffer and whether it is editable.
4283 * @attr ref android.R.styleable#TextView_text
4284 * @attr ref android.R.styleable#TextView_bufferType
4286 public void setText(CharSequence text, BufferType type) {
4287 setText(text, type, true, 0);
4289 if (mCharWrapper != null) {
4290 mCharWrapper.mChars = null;
4294 private void setText(CharSequence text, BufferType type,
4295 boolean notifyBefore, int oldlen) {
4300 // If suggestions are not enabled, remove the suggestion spans from the text
4301 if (!isSuggestionsEnabled()) {
4302 text = removeSuggestionSpans(text);
4305 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4307 if (text instanceof Spanned &&
4308 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4309 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4310 setHorizontalFadingEdgeEnabled(true);
4311 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4313 setHorizontalFadingEdgeEnabled(false);
4314 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4316 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4319 int n = mFilters.length;
4320 for (int i = 0; i < n; i++) {
4321 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4328 if (mText != null) {
4329 oldlen = mText.length();
4330 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4332 sendBeforeTextChanged("", 0, 0, text.length());
4336 boolean needEditableForNotification = false;
4338 if (mListeners != null && mListeners.size() != 0) {
4339 needEditableForNotification = true;
4342 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4343 needEditableForNotification) {
4344 createEditorIfNeeded();
4345 mEditor.forgetUndoRedo();
4346 Editable t = mEditableFactory.newEditable(text);
4348 setFilters(t, mFilters);
4349 InputMethodManager imm = InputMethodManager.peekInstance();
4350 if (imm != null) imm.restartInput(this);
4351 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4352 text = mSpannableFactory.newSpannable(text);
4353 } else if (!(text instanceof CharWrapper)) {
4354 text = TextUtils.stringOrSpannedString(text);
4357 boolean hasClickableSpans = false;
4358 if (mAutoLinkMask != 0) {
4361 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4362 s2 = (Spannable) text;
4364 s2 = mSpannableFactory.newSpannable(text);
4367 hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
4368 if (hasClickableSpans) {
4371 } else if (mLinksClickable && text instanceof Spanned) {
4372 ClickableSpan[] clickableSpans =
4373 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4374 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4375 if (hasClickableSpans && !(text instanceof Spannable)) {
4376 text = mSpannableFactory.newSpannable(text);
4380 if (hasClickableSpans) {
4381 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4383 * We must go ahead and set the text before changing the
4384 * movement method, because setMovementMethod() may call
4385 * setText() again to try to upgrade the buffer type.
4389 // Do not change the movement method for text that supports text selection as it
4390 // would prevent an arbitrary cursor displacement.
4391 if (mLinksClickable && !textCanBeSelected()) {
4392 setMovementMethod(LinkMovementMethod.getInstance());
4399 if (mTransformation == null) {
4400 mTransformed = text;
4402 mTransformed = mTransformation.getTransformation(text, this);
4405 final int textLength = text.length();
4407 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4408 Spannable sp = (Spannable) text;
4410 // Remove any ChangeWatchers that might have come from other TextViews.
4411 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4412 final int count = watchers.length;
4413 for (int i = 0; i < count; i++) {
4414 sp.removeSpan(watchers[i]);
4417 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4419 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4420 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4422 if (mEditor != null) mEditor.addSpanWatchers(sp);
4424 if (mTransformation != null) {
4425 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4428 if (mMovement != null) {
4429 mMovement.initialize(this, (Spannable) text);
4432 * Initializing the movement method will have set the
4433 * selection, so reset mSelectionMoved to keep that from
4434 * interfering with the normal on-focus selection-setting.
4436 if (mEditor != null) mEditor.mSelectionMoved = false;
4440 if (mLayout != null) {
4444 sendOnTextChanged(text, 0, oldlen, textLength);
4445 onTextChanged(text, 0, oldlen, textLength);
4447 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4449 if (needEditableForNotification) {
4450 sendAfterTextChanged((Editable) text);
4453 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4454 if (mEditor != null) mEditor.prepareCursorControllers();
4458 * Sets the TextView to display the specified slice of the specified
4459 * char array. You must promise that you will not change the contents
4460 * of the array except for right before another call to setText(),
4461 * since the TextView has no way to know that the text
4462 * has changed and that it needs to invalidate and re-layout.
4464 public final void setText(char[] text, int start, int len) {
4467 if (start < 0 || len < 0 || start + len > text.length) {
4468 throw new IndexOutOfBoundsException(start + ", " + len);
4472 * We must do the before-notification here ourselves because if
4473 * the old text is a CharWrapper we destroy it before calling
4474 * into the normal path.
4476 if (mText != null) {
4477 oldlen = mText.length();
4478 sendBeforeTextChanged(mText, 0, oldlen, len);
4480 sendBeforeTextChanged("", 0, 0, len);
4483 if (mCharWrapper == null) {
4484 mCharWrapper = new CharWrapper(text, start, len);
4486 mCharWrapper.set(text, start, len);
4489 setText(mCharWrapper, mBufferType, false, oldlen);
4493 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4494 * except that the cursor position (if any) is retained in the new text.
4496 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4498 public final void setTextKeepState(CharSequence text, BufferType type) {
4499 int start = getSelectionStart();
4500 int end = getSelectionEnd();
4501 int len = text.length();
4503 setText(text, type);
4505 if (start >= 0 || end >= 0) {
4506 if (mText instanceof Spannable) {
4507 Selection.setSelection((Spannable) mText,
4508 Math.max(0, Math.min(start, len)),
4509 Math.max(0, Math.min(end, len)));
4514 @android.view.RemotableViewMethod
4515 public final void setText(@StringRes int resid) {
4516 setText(getContext().getResources().getText(resid));
4519 public final void setText(@StringRes int resid, BufferType type) {
4520 setText(getContext().getResources().getText(resid), type);
4524 * Sets the text to be displayed when the text of the TextView is empty.
4525 * Null means to use the normal empty text. The hint does not currently
4526 * participate in determining the size of the view.
4528 * @attr ref android.R.styleable#TextView_hint
4530 @android.view.RemotableViewMethod
4531 public final void setHint(CharSequence hint) {
4532 mHint = TextUtils.stringOrSpannedString(hint);
4534 if (mLayout != null) {
4538 if (mText.length() == 0) {
4542 // Invalidate display list if hint is currently used
4543 if (mEditor != null && mText.length() == 0 && mHint != null) {
4544 mEditor.invalidateTextDisplayList();
4549 * Sets the text to be displayed when the text of the TextView is empty,
4552 * @attr ref android.R.styleable#TextView_hint
4554 @android.view.RemotableViewMethod
4555 public final void setHint(@StringRes int resid) {
4556 setHint(getContext().getResources().getText(resid));
4560 * Returns the hint that is displayed when the text of the TextView
4563 * @attr ref android.R.styleable#TextView_hint
4565 @ViewDebug.CapturedViewProperty
4566 public CharSequence getHint() {
4570 boolean isSingleLine() {
4574 private static boolean isMultilineInputType(int type) {
4575 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4576 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4580 * Removes the suggestion spans.
4582 CharSequence removeSuggestionSpans(CharSequence text) {
4583 if (text instanceof Spanned) {
4584 Spannable spannable;
4585 if (text instanceof Spannable) {
4586 spannable = (Spannable) text;
4588 spannable = new SpannableString(text);
4592 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4593 for (int i = 0; i < spans.length; i++) {
4594 spannable.removeSpan(spans[i]);
4601 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4602 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4603 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4604 * then a soft keyboard will not be displayed for this text view.
4606 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4607 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4610 * @see #getInputType()
4611 * @see #setRawInputType(int)
4612 * @see android.text.InputType
4613 * @attr ref android.R.styleable#TextView_inputType
4615 public void setInputType(int type) {
4616 final boolean wasPassword = isPasswordInputType(getInputType());
4617 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4618 setInputType(type, false);
4619 final boolean isPassword = isPasswordInputType(type);
4620 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4621 boolean forceUpdate = false;
4623 setTransformationMethod(PasswordTransformationMethod.getInstance());
4624 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4625 } else if (isVisiblePassword) {
4626 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4629 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4630 } else if (wasPassword || wasVisiblePassword) {
4631 // not in password mode, clean up typeface and transformation
4632 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4633 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4638 boolean singleLine = !isMultilineInputType(type);
4640 // We need to update the single line mode if it has changed or we
4641 // were previously in password mode.
4642 if (mSingleLine != singleLine || forceUpdate) {
4643 // Change single line mode, but only change the transformation if
4644 // we are not in password mode.
4645 applySingleLine(singleLine, !isPassword, true);
4648 if (!isSuggestionsEnabled()) {
4649 mText = removeSuggestionSpans(mText);
4652 InputMethodManager imm = InputMethodManager.peekInstance();
4653 if (imm != null) imm.restartInput(this);
4657 * It would be better to rely on the input type for everything. A password inputType should have
4658 * a password transformation. We should hence use isPasswordInputType instead of this method.
4661 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4662 * would install the correct transformation).
4663 * - Refuse the installation of a non-password transformation in setTransformation if the input
4666 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4667 * is useful since it matches what the user can see (obfuscated text or not).
4669 * @return true if the current transformation method is of the password type.
4671 boolean hasPasswordTransformationMethod() {
4672 return mTransformation instanceof PasswordTransformationMethod;
4675 private static boolean isPasswordInputType(int inputType) {
4676 final int variation =
4677 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4679 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4681 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4683 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4686 private static boolean isVisiblePasswordInputType(int inputType) {
4687 final int variation =
4688 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4690 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4694 * Directly change the content type integer of the text view, without
4695 * modifying any other state.
4696 * @see #setInputType(int)
4697 * @see android.text.InputType
4698 * @attr ref android.R.styleable#TextView_inputType
4700 public void setRawInputType(int type) {
4701 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4702 createEditorIfNeeded();
4703 mEditor.mInputType = type;
4706 private void setInputType(int type, boolean direct) {
4707 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4709 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4710 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4711 TextKeyListener.Capitalize cap;
4712 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4713 cap = TextKeyListener.Capitalize.CHARACTERS;
4714 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4715 cap = TextKeyListener.Capitalize.WORDS;
4716 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4717 cap = TextKeyListener.Capitalize.SENTENCES;
4719 cap = TextKeyListener.Capitalize.NONE;
4721 input = TextKeyListener.getInstance(autotext, cap);
4722 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4723 input = DigitsKeyListener.getInstance(
4724 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4725 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4726 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4727 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4728 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4729 input = DateKeyListener.getInstance();
4731 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4732 input = TimeKeyListener.getInstance();
4735 input = DateTimeKeyListener.getInstance();
4738 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4739 input = DialerKeyListener.getInstance();
4741 input = TextKeyListener.getInstance();
4743 setRawInputType(type);
4745 createEditorIfNeeded();
4746 mEditor.mKeyListener = input;
4748 setKeyListenerOnly(input);
4753 * Get the type of the editable content.
4755 * @see #setInputType(int)
4756 * @see android.text.InputType
4758 public int getInputType() {
4759 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4763 * Change the editor type integer associated with the text view, which
4764 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4766 * @see #getImeOptions
4767 * @see android.view.inputmethod.EditorInfo
4768 * @attr ref android.R.styleable#TextView_imeOptions
4770 public void setImeOptions(int imeOptions) {
4771 createEditorIfNeeded();
4772 mEditor.createInputContentTypeIfNeeded();
4773 mEditor.mInputContentType.imeOptions = imeOptions;
4777 * Get the type of the IME editor.
4779 * @see #setImeOptions(int)
4780 * @see android.view.inputmethod.EditorInfo
4782 public int getImeOptions() {
4783 return mEditor != null && mEditor.mInputContentType != null
4784 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4788 * Change the custom IME action associated with the text view, which
4789 * will be reported to an IME with {@link EditorInfo#actionLabel}
4790 * and {@link EditorInfo#actionId} when it has focus.
4791 * @see #getImeActionLabel
4792 * @see #getImeActionId
4793 * @see android.view.inputmethod.EditorInfo
4794 * @attr ref android.R.styleable#TextView_imeActionLabel
4795 * @attr ref android.R.styleable#TextView_imeActionId
4797 public void setImeActionLabel(CharSequence label, int actionId) {
4798 createEditorIfNeeded();
4799 mEditor.createInputContentTypeIfNeeded();
4800 mEditor.mInputContentType.imeActionLabel = label;
4801 mEditor.mInputContentType.imeActionId = actionId;
4805 * Get the IME action label previous set with {@link #setImeActionLabel}.
4807 * @see #setImeActionLabel
4808 * @see android.view.inputmethod.EditorInfo
4810 public CharSequence getImeActionLabel() {
4811 return mEditor != null && mEditor.mInputContentType != null
4812 ? mEditor.mInputContentType.imeActionLabel : null;
4816 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4818 * @see #setImeActionLabel
4819 * @see android.view.inputmethod.EditorInfo
4821 public int getImeActionId() {
4822 return mEditor != null && mEditor.mInputContentType != null
4823 ? mEditor.mInputContentType.imeActionId : 0;
4827 * Set a special listener to be called when an action is performed
4828 * on the text view. This will be called when the enter key is pressed,
4829 * or when an action supplied to the IME is selected by the user. Setting
4830 * this means that the normal hard key event will not insert a newline
4831 * into the text view, even if it is multi-line; holding down the ALT
4832 * modifier will, however, allow the user to insert a newline character.
4834 public void setOnEditorActionListener(OnEditorActionListener l) {
4835 createEditorIfNeeded();
4836 mEditor.createInputContentTypeIfNeeded();
4837 mEditor.mInputContentType.onEditorActionListener = l;
4841 * Called when an attached input method calls
4842 * {@link InputConnection#performEditorAction(int)
4843 * InputConnection.performEditorAction()}
4844 * for this text view. The default implementation will call your action
4845 * listener supplied to {@link #setOnEditorActionListener}, or perform
4846 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4847 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4848 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4849 * EditorInfo.IME_ACTION_DONE}.
4851 * <p>For backwards compatibility, if no IME options have been set and the
4852 * text view would not normally advance focus on enter, then
4853 * the NEXT and DONE actions received here will be turned into an enter
4854 * key down/up pair to go through the normal key handling.
4856 * @param actionCode The code of the action being performed.
4858 * @see #setOnEditorActionListener
4860 public void onEditorAction(int actionCode) {
4861 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4863 if (ict.onEditorActionListener != null) {
4864 if (ict.onEditorActionListener.onEditorAction(this,
4865 actionCode, null)) {
4870 // This is the handling for some default action.
4871 // Note that for backwards compatibility we don't do this
4872 // default handling if explicit ime options have not been given,
4873 // instead turning this into the normal enter key codes that an
4874 // app may be expecting.
4875 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4876 View v = focusSearch(FOCUS_FORWARD);
4878 if (!v.requestFocus(FOCUS_FORWARD)) {
4879 throw new IllegalStateException("focus search returned a view " +
4880 "that wasn't able to take focus!");
4885 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4886 View v = focusSearch(FOCUS_BACKWARD);
4888 if (!v.requestFocus(FOCUS_BACKWARD)) {
4889 throw new IllegalStateException("focus search returned a view " +
4890 "that wasn't able to take focus!");
4895 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4896 InputMethodManager imm = InputMethodManager.peekInstance();
4897 if (imm != null && imm.isActive(this)) {
4898 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4904 ViewRootImpl viewRootImpl = getViewRootImpl();
4905 if (viewRootImpl != null) {
4906 long eventTime = SystemClock.uptimeMillis();
4907 viewRootImpl.dispatchKeyFromIme(
4908 new KeyEvent(eventTime, eventTime,
4909 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4910 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4911 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4912 | KeyEvent.FLAG_EDITOR_ACTION));
4913 viewRootImpl.dispatchKeyFromIme(
4914 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4915 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4916 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4917 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4918 | KeyEvent.FLAG_EDITOR_ACTION));
4923 * Set the private content type of the text, which is the
4924 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4925 * field that will be filled in when creating an input connection.
4927 * @see #getPrivateImeOptions()
4928 * @see EditorInfo#privateImeOptions
4929 * @attr ref android.R.styleable#TextView_privateImeOptions
4931 public void setPrivateImeOptions(String type) {
4932 createEditorIfNeeded();
4933 mEditor.createInputContentTypeIfNeeded();
4934 mEditor.mInputContentType.privateImeOptions = type;
4938 * Get the private type of the content.
4940 * @see #setPrivateImeOptions(String)
4941 * @see EditorInfo#privateImeOptions
4943 public String getPrivateImeOptions() {
4944 return mEditor != null && mEditor.mInputContentType != null
4945 ? mEditor.mInputContentType.privateImeOptions : null;
4949 * Set the extra input data of the text, which is the
4950 * {@link EditorInfo#extras TextBoxAttribute.extras}
4951 * Bundle that will be filled in when creating an input connection. The
4952 * given integer is the resource ID of an XML resource holding an
4953 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4955 * @see #getInputExtras(boolean)
4956 * @see EditorInfo#extras
4957 * @attr ref android.R.styleable#TextView_editorExtras
4959 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4960 createEditorIfNeeded();
4961 XmlResourceParser parser = getResources().getXml(xmlResId);
4962 mEditor.createInputContentTypeIfNeeded();
4963 mEditor.mInputContentType.extras = new Bundle();
4964 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
4968 * Retrieve the input extras currently associated with the text view, which
4969 * can be viewed as well as modified.
4971 * @param create If true, the extras will be created if they don't already
4972 * exist. Otherwise, null will be returned if none have been created.
4973 * @see #setInputExtras(int)
4974 * @see EditorInfo#extras
4975 * @attr ref android.R.styleable#TextView_editorExtras
4977 public Bundle getInputExtras(boolean create) {
4978 if (mEditor == null && !create) return null;
4979 createEditorIfNeeded();
4980 if (mEditor.mInputContentType == null) {
4981 if (!create) return null;
4982 mEditor.createInputContentTypeIfNeeded();
4984 if (mEditor.mInputContentType.extras == null) {
4985 if (!create) return null;
4986 mEditor.mInputContentType.extras = new Bundle();
4988 return mEditor.mInputContentType.extras;
4992 * Returns the error message that was set to be displayed with
4993 * {@link #setError}, or <code>null</code> if no error was set
4994 * or if it the error was cleared by the widget after user input.
4996 public CharSequence getError() {
4997 return mEditor == null ? null : mEditor.mError;
5001 * Sets the right-hand compound drawable of the TextView to the "error"
5002 * icon and sets an error message that will be displayed in a popup when
5003 * the TextView has focus. The icon and error message will be reset to
5004 * null when any key events cause changes to the TextView's text. If the
5005 * <code>error</code> is <code>null</code>, the error message and icon
5008 @android.view.RemotableViewMethod
5009 public void setError(CharSequence error) {
5010 if (error == null) {
5011 setError(null, null);
5013 Drawable dr = getContext().getDrawable(
5014 com.android.internal.R.drawable.indicator_input_error);
5016 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5017 setError(error, dr);
5022 * Sets the right-hand compound drawable of the TextView to the specified
5023 * icon and sets an error message that will be displayed in a popup when
5024 * the TextView has focus. The icon and error message will be reset to
5025 * null when any key events cause changes to the TextView's text. The
5026 * drawable must already have had {@link Drawable#setBounds} set on it.
5027 * If the <code>error</code> is <code>null</code>, the error message will
5028 * be cleared (and you should provide a <code>null</code> icon as well).
5030 public void setError(CharSequence error, Drawable icon) {
5031 createEditorIfNeeded();
5032 mEditor.setError(error, icon);
5033 notifyViewAccessibilityStateChangedIfNeeded(
5034 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5038 protected boolean setFrame(int l, int t, int r, int b) {
5039 boolean result = super.setFrame(l, t, r, b);
5041 if (mEditor != null) mEditor.setFrame();
5043 restartMarqueeIfNeeded();
5048 private void restartMarqueeIfNeeded() {
5049 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5050 mRestartMarquee = false;
5056 * Sets the list of input filters that will be used if the buffer is
5057 * Editable. Has no effect otherwise.
5059 * @attr ref android.R.styleable#TextView_maxLength
5061 public void setFilters(InputFilter[] filters) {
5062 if (filters == null) {
5063 throw new IllegalArgumentException();
5068 if (mText instanceof Editable) {
5069 setFilters((Editable) mText, filters);
5074 * Sets the list of input filters on the specified Editable,
5075 * and includes mInput in the list if it is an InputFilter.
5077 private void setFilters(Editable e, InputFilter[] filters) {
5078 if (mEditor != null) {
5079 final boolean undoFilter = mEditor.mUndoInputFilter != null;
5080 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5082 if (undoFilter) num++;
5083 if (keyFilter) num++;
5085 InputFilter[] nf = new InputFilter[filters.length + num];
5087 System.arraycopy(filters, 0, nf, 0, filters.length);
5090 nf[filters.length] = mEditor.mUndoInputFilter;
5094 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5101 e.setFilters(filters);
5105 * Returns the current list of input filters.
5107 * @attr ref android.R.styleable#TextView_maxLength
5109 public InputFilter[] getFilters() {
5113 /////////////////////////////////////////////////////////////////////////
5115 private int getBoxHeight(Layout l) {
5116 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5117 int padding = (l == mHintLayout) ?
5118 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5119 getExtendedPaddingTop() + getExtendedPaddingBottom();
5120 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5123 int getVerticalOffset(boolean forceNormal) {
5125 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5128 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5132 if (gravity != Gravity.TOP) {
5133 int boxht = getBoxHeight(l);
5134 int textht = l.getHeight();
5136 if (textht < boxht) {
5137 if (gravity == Gravity.BOTTOM)
5138 voffset = boxht - textht;
5139 else // (gravity == Gravity.CENTER_VERTICAL)
5140 voffset = (boxht - textht) >> 1;
5146 private int getBottomVerticalOffset(boolean forceNormal) {
5148 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5151 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5155 if (gravity != Gravity.BOTTOM) {
5156 int boxht = getBoxHeight(l);
5157 int textht = l.getHeight();
5159 if (textht < boxht) {
5160 if (gravity == Gravity.TOP)
5161 voffset = boxht - textht;
5162 else // (gravity == Gravity.CENTER_VERTICAL)
5163 voffset = (boxht - textht) >> 1;
5169 void invalidateCursorPath() {
5170 if (mHighlightPathBogus) {
5173 final int horizontalPadding = getCompoundPaddingLeft();
5174 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5176 if (mEditor.mCursorCount == 0) {
5177 synchronized (TEMP_RECTF) {
5179 * The reason for this concern about the thickness of the
5180 * cursor and doing the floor/ceil on the coordinates is that
5181 * some EditTexts (notably textfields in the Browser) have
5182 * anti-aliased text where not all the characters are
5183 * necessarily at integer-multiple locations. This should
5184 * make sure the entire cursor gets invalidated instead of
5185 * sometimes missing half a pixel.
5187 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5194 // mHighlightPath is guaranteed to be non null at that point.
5195 mHighlightPath.computeBounds(TEMP_RECTF, false);
5197 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5198 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5199 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5200 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5203 for (int i = 0; i < mEditor.mCursorCount; i++) {
5204 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5205 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5206 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5212 void invalidateCursor() {
5213 int where = getSelectionEnd();
5215 invalidateCursor(where, where, where);
5218 private void invalidateCursor(int a, int b, int c) {
5219 if (a >= 0 || b >= 0 || c >= 0) {
5220 int start = Math.min(Math.min(a, b), c);
5221 int end = Math.max(Math.max(a, b), c);
5222 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5227 * Invalidates the region of text enclosed between the start and end text offsets.
5229 void invalidateRegion(int start, int end, boolean invalidateCursor) {
5230 if (mLayout == null) {
5233 int lineStart = mLayout.getLineForOffset(start);
5234 int top = mLayout.getLineTop(lineStart);
5236 // This is ridiculous, but the descent from the line above
5237 // can hang down into the line we really want to redraw,
5238 // so we have to invalidate part of the line above to make
5239 // sure everything that needs to be redrawn really is.
5240 // (But not the whole line above, because that would cause
5241 // the same problem with the descenders on the line above it!)
5242 if (lineStart > 0) {
5243 top -= mLayout.getLineDescent(lineStart - 1);
5249 lineEnd = lineStart;
5251 lineEnd = mLayout.getLineForOffset(end);
5253 int bottom = mLayout.getLineBottom(lineEnd);
5255 // mEditor can be null in case selection is set programmatically.
5256 if (invalidateCursor && mEditor != null) {
5257 for (int i = 0; i < mEditor.mCursorCount; i++) {
5258 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5259 top = Math.min(top, bounds.top);
5260 bottom = Math.max(bottom, bounds.bottom);
5264 final int compoundPaddingLeft = getCompoundPaddingLeft();
5265 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5268 if (lineStart == lineEnd && !invalidateCursor) {
5269 left = (int) mLayout.getPrimaryHorizontal(start);
5270 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5271 left += compoundPaddingLeft;
5272 right += compoundPaddingLeft;
5274 // Rectangle bounding box when the region spans several lines
5275 left = compoundPaddingLeft;
5276 right = getWidth() - getCompoundPaddingRight();
5279 invalidate(mScrollX + left, verticalPadding + top,
5280 mScrollX + right, verticalPadding + bottom);
5284 private void registerForPreDraw() {
5285 if (!mPreDrawRegistered) {
5286 getViewTreeObserver().addOnPreDrawListener(this);
5287 mPreDrawRegistered = true;
5291 private void unregisterForPreDraw() {
5292 getViewTreeObserver().removeOnPreDrawListener(this);
5293 mPreDrawRegistered = false;
5294 mPreDrawListenerDetached = false;
5300 public boolean onPreDraw() {
5301 if (mLayout == null) {
5305 if (mMovement != null) {
5306 /* This code also provides auto-scrolling when a cursor is moved using a
5307 * CursorController (insertion point or selection limits).
5308 * For selection, ensure start or end is visible depending on controller's state.
5310 int curs = getSelectionEnd();
5311 // Do not create the controller if it is not already created.
5312 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5313 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5314 curs = getSelectionStart();
5318 * TODO: This should really only keep the end in view if
5319 * it already was before the text changed. I'm not sure
5320 * of a good way to tell from here if it was.
5322 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5323 curs = mText.length();
5327 bringPointIntoView(curs);
5330 bringTextIntoView();
5333 // This has to be checked here since:
5334 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5335 // a screen rotation) since layout is not yet initialized at that point.
5336 if (mEditor != null && mEditor.mCreatedWithASelection) {
5337 if (mEditor.extractedTextModeWillBeStarted()) {
5338 mEditor.checkFieldAndSelectCurrentWord();
5340 mEditor.startSelectionActionMode();
5342 mEditor.mCreatedWithASelection = false;
5345 unregisterForPreDraw();
5351 protected void onAttachedToWindow() {
5352 super.onAttachedToWindow();
5354 mTemporaryDetach = false;
5356 if (mEditor != null) mEditor.onAttachedToWindow();
5358 if (mPreDrawListenerDetached) {
5359 getViewTreeObserver().addOnPreDrawListener(this);
5360 mPreDrawListenerDetached = false;
5366 protected void onDetachedFromWindowInternal() {
5367 if (mPreDrawRegistered) {
5368 getViewTreeObserver().removeOnPreDrawListener(this);
5369 mPreDrawListenerDetached = true;
5372 resetResolvedDrawables();
5374 if (mEditor != null) mEditor.onDetachedFromWindow();
5376 super.onDetachedFromWindowInternal();
5380 public void onScreenStateChanged(int screenState) {
5381 super.onScreenStateChanged(screenState);
5382 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5386 protected boolean isPaddingOffsetRequired() {
5387 return mShadowRadius != 0 || mDrawables != null;
5391 protected int getLeftPaddingOffset() {
5392 return getCompoundPaddingLeft() - mPaddingLeft +
5393 (int) Math.min(0, mShadowDx - mShadowRadius);
5397 protected int getTopPaddingOffset() {
5398 return (int) Math.min(0, mShadowDy - mShadowRadius);
5402 protected int getBottomPaddingOffset() {
5403 return (int) Math.max(0, mShadowDy + mShadowRadius);
5406 private int getFudgedPaddingRight() {
5407 // Add sufficient space for cursor and tone marks
5408 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5409 return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5413 protected int getRightPaddingOffset() {
5414 return -(getFudgedPaddingRight() - mPaddingRight) +
5415 (int) Math.max(0, mShadowDx + mShadowRadius);
5419 protected boolean verifyDrawable(Drawable who) {
5420 final boolean verified = super.verifyDrawable(who);
5421 if (!verified && mDrawables != null) {
5422 for (Drawable dr : mDrawables.mShowing) {
5432 public void jumpDrawablesToCurrentState() {
5433 super.jumpDrawablesToCurrentState();
5434 if (mDrawables != null) {
5435 for (Drawable dr : mDrawables.mShowing) {
5437 dr.jumpToCurrentState();
5444 public void invalidateDrawable(Drawable drawable) {
5445 boolean handled = false;
5447 if (verifyDrawable(drawable)) {
5448 final Rect dirty = drawable.getBounds();
5449 int scrollX = mScrollX;
5450 int scrollY = mScrollY;
5452 // IMPORTANT: The coordinates below are based on the coordinates computed
5453 // for each compound drawable in onDraw(). Make sure to update each section
5455 final TextView.Drawables drawables = mDrawables;
5456 if (drawables != null) {
5457 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5458 final int compoundPaddingTop = getCompoundPaddingTop();
5459 final int compoundPaddingBottom = getCompoundPaddingBottom();
5460 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5462 scrollX += mPaddingLeft;
5463 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5465 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5466 final int compoundPaddingTop = getCompoundPaddingTop();
5467 final int compoundPaddingBottom = getCompoundPaddingBottom();
5468 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5470 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5471 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5473 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5474 final int compoundPaddingLeft = getCompoundPaddingLeft();
5475 final int compoundPaddingRight = getCompoundPaddingRight();
5476 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5478 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5479 scrollY += mPaddingTop;
5481 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5482 final int compoundPaddingLeft = getCompoundPaddingLeft();
5483 final int compoundPaddingRight = getCompoundPaddingRight();
5484 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5486 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5487 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5493 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5494 dirty.right + scrollX, dirty.bottom + scrollY);
5499 super.invalidateDrawable(drawable);
5504 public boolean hasOverlappingRendering() {
5505 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5506 return ((getBackground() != null && getBackground().getCurrent() != null)
5507 || mText instanceof Spannable || hasSelection()
5508 || isHorizontalFadingEdgeEnabled());
5513 * Returns the state of the {@code textIsSelectable} flag (See
5514 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5515 * to allow users to select and copy text in a non-editable TextView, the content of an
5516 * {@link EditText} can always be selected, independently of the value of this flag.
5519 * @return True if the text displayed in this TextView can be selected by the user.
5521 * @attr ref android.R.styleable#TextView_textIsSelectable
5523 public boolean isTextSelectable() {
5524 return mEditor == null ? false : mEditor.mTextIsSelectable;
5528 * Sets whether the content of this view is selectable by the user. The default is
5529 * {@code false}, meaning that the content is not selectable.
5531 * When you use a TextView to display a useful piece of information to the user (such as a
5532 * contact's address), make it selectable, so that the user can select and copy its
5533 * content. You can also use set the XML attribute
5534 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5536 * When you call this method to set the value of {@code textIsSelectable}, it sets
5537 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5538 * and {@code longClickable} to the same value. These flags correspond to the attributes
5539 * {@link android.R.styleable#View_focusable android:focusable},
5540 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5541 * {@link android.R.styleable#View_clickable android:clickable}, and
5542 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5543 * flags to a state you had set previously, call one or more of the following methods:
5544 * {@link #setFocusable(boolean) setFocusable()},
5545 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5546 * {@link #setClickable(boolean) setClickable()} or
5547 * {@link #setLongClickable(boolean) setLongClickable()}.
5549 * @param selectable Whether the content of this TextView should be selectable.
5551 public void setTextIsSelectable(boolean selectable) {
5552 if (!selectable && mEditor == null) return; // false is default value with no edit data
5554 createEditorIfNeeded();
5555 if (mEditor.mTextIsSelectable == selectable) return;
5557 mEditor.mTextIsSelectable = selectable;
5558 setFocusableInTouchMode(selectable);
5559 setFocusable(selectable);
5560 setClickable(selectable);
5561 setLongClickable(selectable);
5563 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5565 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5566 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5568 // Called by setText above, but safer in case of future code changes
5569 mEditor.prepareCursorControllers();
5573 protected int[] onCreateDrawableState(int extraSpace) {
5574 final int[] drawableState;
5577 drawableState = super.onCreateDrawableState(extraSpace);
5579 drawableState = super.onCreateDrawableState(extraSpace + 1);
5580 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5583 if (isTextSelectable()) {
5584 // Disable pressed state, which was introduced when TextView was made clickable.
5585 // Prevents text color change.
5586 // setClickable(false) would have a similar effect, but it also disables focus changes
5587 // and long press actions, which are both needed by text selection.
5588 final int length = drawableState.length;
5589 for (int i = 0; i < length; i++) {
5590 if (drawableState[i] == R.attr.state_pressed) {
5591 final int[] nonPressedState = new int[length - 1];
5592 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5593 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5594 return nonPressedState;
5599 return drawableState;
5602 private Path getUpdatedHighlightPath() {
5603 Path highlight = null;
5604 Paint highlightPaint = mHighlightPaint;
5606 final int selStart = getSelectionStart();
5607 final int selEnd = getSelectionEnd();
5608 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5609 if (selStart == selEnd) {
5610 if (mEditor != null && mEditor.isCursorVisible() &&
5611 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5612 (2 * Editor.BLINK) < Editor.BLINK) {
5613 if (mHighlightPathBogus) {
5614 if (mHighlightPath == null) mHighlightPath = new Path();
5615 mHighlightPath.reset();
5616 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5617 mEditor.updateCursorsPositions();
5618 mHighlightPathBogus = false;
5621 // XXX should pass to skin instead of drawing directly
5622 highlightPaint.setColor(mCurTextColor);
5623 highlightPaint.setStyle(Paint.Style.STROKE);
5624 highlight = mHighlightPath;
5627 if (mHighlightPathBogus) {
5628 if (mHighlightPath == null) mHighlightPath = new Path();
5629 mHighlightPath.reset();
5630 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5631 mHighlightPathBogus = false;
5634 // XXX should pass to skin instead of drawing directly
5635 highlightPaint.setColor(mHighlightColor);
5636 highlightPaint.setStyle(Paint.Style.FILL);
5638 highlight = mHighlightPath;
5647 public int getHorizontalOffsetForDrawables() {
5652 protected void onDraw(Canvas canvas) {
5653 restartMarqueeIfNeeded();
5655 // Draw the background for this view
5656 super.onDraw(canvas);
5658 final int compoundPaddingLeft = getCompoundPaddingLeft();
5659 final int compoundPaddingTop = getCompoundPaddingTop();
5660 final int compoundPaddingRight = getCompoundPaddingRight();
5661 final int compoundPaddingBottom = getCompoundPaddingBottom();
5662 final int scrollX = mScrollX;
5663 final int scrollY = mScrollY;
5664 final int right = mRight;
5665 final int left = mLeft;
5666 final int bottom = mBottom;
5667 final int top = mTop;
5668 final boolean isLayoutRtl = isLayoutRtl();
5669 final int offset = getHorizontalOffsetForDrawables();
5670 final int leftOffset = isLayoutRtl ? 0 : offset;
5671 final int rightOffset = isLayoutRtl ? offset : 0 ;
5673 final Drawables dr = mDrawables;
5676 * Compound, not extended, because the icon is not clipped
5677 * if the text height is smaller.
5680 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5681 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5683 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5684 // Make sure to update invalidateDrawable() when changing this code.
5685 if (dr.mShowing[Drawables.LEFT] != null) {
5687 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5688 scrollY + compoundPaddingTop +
5689 (vspace - dr.mDrawableHeightLeft) / 2);
5690 dr.mShowing[Drawables.LEFT].draw(canvas);
5694 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5695 // Make sure to update invalidateDrawable() when changing this code.
5696 if (dr.mShowing[Drawables.RIGHT] != null) {
5698 canvas.translate(scrollX + right - left - mPaddingRight
5699 - dr.mDrawableSizeRight - rightOffset,
5700 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5701 dr.mShowing[Drawables.RIGHT].draw(canvas);
5705 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5706 // Make sure to update invalidateDrawable() when changing this code.
5707 if (dr.mShowing[Drawables.TOP] != null) {
5709 canvas.translate(scrollX + compoundPaddingLeft +
5710 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5711 dr.mShowing[Drawables.TOP].draw(canvas);
5715 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5716 // Make sure to update invalidateDrawable() when changing this code.
5717 if (dr.mShowing[Drawables.BOTTOM] != null) {
5719 canvas.translate(scrollX + compoundPaddingLeft +
5720 (hspace - dr.mDrawableWidthBottom) / 2,
5721 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5722 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5727 int color = mCurTextColor;
5729 if (mLayout == null) {
5733 Layout layout = mLayout;
5735 if (mHint != null && mText.length() == 0) {
5736 if (mHintTextColor != null) {
5737 color = mCurHintTextColor;
5740 layout = mHintLayout;
5743 mTextPaint.setColor(color);
5744 mTextPaint.drawableState = getDrawableState();
5747 /* Would be faster if we didn't have to do this. Can we chop the
5748 (displayable) text so that we don't need to do this ever?
5751 int extendedPaddingTop = getExtendedPaddingTop();
5752 int extendedPaddingBottom = getExtendedPaddingBottom();
5754 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5755 final int maxScrollY = mLayout.getHeight() - vspace;
5757 float clipLeft = compoundPaddingLeft + scrollX;
5758 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5759 float clipRight = right - left - getFudgedPaddingRight() + scrollX;
5760 float clipBottom = bottom - top + scrollY -
5761 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5763 if (mShadowRadius != 0) {
5764 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5765 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5767 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5768 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5771 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5773 int voffsetText = 0;
5774 int voffsetCursor = 0;
5776 // translate in by our padding
5777 /* shortcircuit calling getVerticaOffset() */
5778 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5779 voffsetText = getVerticalOffset(false);
5780 voffsetCursor = getVerticalOffset(true);
5782 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5784 final int layoutDirection = getLayoutDirection();
5785 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5786 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5787 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5788 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5789 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5790 final int width = mRight - mLeft;
5791 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5792 final float dx = mLayout.getLineRight(0) - (width - padding);
5793 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5796 if (mMarquee != null && mMarquee.isRunning()) {
5797 final float dx = -mMarquee.getScroll();
5798 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5802 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5804 Path highlight = getUpdatedHighlightPath();
5805 if (mEditor != null) {
5806 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5808 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5811 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5812 final float dx = mMarquee.getGhostOffset();
5813 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5814 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5821 public void getFocusedRect(Rect r) {
5822 if (mLayout == null) {
5823 super.getFocusedRect(r);
5827 int selEnd = getSelectionEnd();
5829 super.getFocusedRect(r);
5833 int selStart = getSelectionStart();
5834 if (selStart < 0 || selStart >= selEnd) {
5835 int line = mLayout.getLineForOffset(selEnd);
5836 r.top = mLayout.getLineTop(line);
5837 r.bottom = mLayout.getLineBottom(line);
5838 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5839 r.right = r.left + 4;
5841 int lineStart = mLayout.getLineForOffset(selStart);
5842 int lineEnd = mLayout.getLineForOffset(selEnd);
5843 r.top = mLayout.getLineTop(lineStart);
5844 r.bottom = mLayout.getLineBottom(lineEnd);
5845 if (lineStart == lineEnd) {
5846 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5847 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5849 // Selection extends across multiple lines -- make the focused
5850 // rect cover the entire width.
5851 if (mHighlightPathBogus) {
5852 if (mHighlightPath == null) mHighlightPath = new Path();
5853 mHighlightPath.reset();
5854 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5855 mHighlightPathBogus = false;
5857 synchronized (TEMP_RECTF) {
5858 mHighlightPath.computeBounds(TEMP_RECTF, true);
5859 r.left = (int)TEMP_RECTF.left-1;
5860 r.right = (int)TEMP_RECTF.right+1;
5865 // Adjust for padding and gravity.
5866 int paddingLeft = getCompoundPaddingLeft();
5867 int paddingTop = getExtendedPaddingTop();
5868 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5869 paddingTop += getVerticalOffset(false);
5871 r.offset(paddingLeft, paddingTop);
5872 int paddingBottom = getExtendedPaddingBottom();
5873 r.bottom += paddingBottom;
5877 * Return the number of lines of text, or 0 if the internal Layout has not
5880 public int getLineCount() {
5881 return mLayout != null ? mLayout.getLineCount() : 0;
5885 * Return the baseline for the specified line (0...getLineCount() - 1)
5886 * If bounds is not null, return the top, left, right, bottom extents
5887 * of the specified line in it. If the internal Layout has not been built,
5888 * return 0 and set bounds to (0, 0, 0, 0)
5889 * @param line which line to examine (0..getLineCount() - 1)
5890 * @param bounds Optional. If not null, it returns the extent of the line
5891 * @return the Y-coordinate of the baseline
5893 public int getLineBounds(int line, Rect bounds) {
5894 if (mLayout == null) {
5895 if (bounds != null) {
5896 bounds.set(0, 0, 0, 0);
5901 int baseline = mLayout.getLineBounds(line, bounds);
5903 int voffset = getExtendedPaddingTop();
5904 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5905 voffset += getVerticalOffset(true);
5907 if (bounds != null) {
5908 bounds.offset(getCompoundPaddingLeft(), voffset);
5910 return baseline + voffset;
5915 public int getBaseline() {
5916 if (mLayout == null) {
5917 return super.getBaseline();
5920 return getBaselineOffset() + mLayout.getLineBaseline(0);
5923 int getBaselineOffset() {
5925 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5926 voffset = getVerticalOffset(true);
5929 if (isLayoutModeOptical(mParent)) {
5930 voffset -= getOpticalInsets().top;
5933 return getExtendedPaddingTop() + voffset;
5940 protected int getFadeTop(boolean offsetRequired) {
5941 if (mLayout == null) return 0;
5944 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5945 voffset = getVerticalOffset(true);
5948 if (offsetRequired) voffset += getTopPaddingOffset();
5950 return getExtendedPaddingTop() + voffset;
5957 protected int getFadeHeight(boolean offsetRequired) {
5958 return mLayout != null ? mLayout.getHeight() : 0;
5965 public int getPointerShape(MotionEvent event, float x, float y) {
5966 if (isTextSelectable() || isTextEditable()) {
5967 return PointerIcon.STYLE_TEXT;
5969 return super.getPointerShape(event, x, y);
5973 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5974 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
5975 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
5976 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
5977 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
5980 return super.onKeyPreIme(keyCode, event);
5986 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
5987 // Do nothing unless mEditor is in text action mode.
5988 if (mEditor == null || mEditor.mTextActionMode == null) {
5992 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5993 KeyEvent.DispatcherState state = getKeyDispatcherState();
5994 if (state != null) {
5995 state.startTracking(event, this);
5998 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5999 KeyEvent.DispatcherState state = getKeyDispatcherState();
6000 if (state != null) {
6001 state.handleUpEvent(event);
6003 if (event.isTracking() && !event.isCanceled()) {
6004 stopTextActionMode();
6012 public boolean onKeyDown(int keyCode, KeyEvent event) {
6013 final int which = doKeyDown(keyCode, event, null);
6014 if (which == KEY_EVENT_NOT_HANDLED) {
6015 return super.onKeyDown(keyCode, event);
6022 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
6023 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
6024 final int which = doKeyDown(keyCode, down, event);
6025 if (which == KEY_EVENT_NOT_HANDLED) {
6026 // Go through default dispatching.
6027 return super.onKeyMultiple(keyCode, repeatCount, event);
6029 if (which == KEY_EVENT_HANDLED) {
6030 // Consumed the whole thing.
6036 // We are going to dispatch the remaining events to either the input
6037 // or movement method. To do this, we will just send a repeated stream
6038 // of down and up events until we have done the complete repeatCount.
6039 // It would be nice if those interfaces had an onKeyMultiple() method,
6040 // but adding that is a more complicated change.
6041 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
6042 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
6043 // mEditor and mEditor.mInput are not null from doKeyDown
6044 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6045 while (--repeatCount > 0) {
6046 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6047 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6049 hideErrorIfUnchanged();
6051 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
6052 // mMovement is not null from doKeyDown
6053 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6054 while (--repeatCount > 0) {
6055 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6056 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6064 * Returns true if pressing ENTER in this field advances focus instead
6065 * of inserting the character. This is true mostly in single-line fields,
6066 * but also in mail addresses and subjects which will display on multiple
6067 * lines but where it doesn't make sense to insert newlines.
6069 private boolean shouldAdvanceFocusOnEnter() {
6070 if (getKeyListener() == null) {
6078 if (mEditor != null &&
6079 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6080 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6081 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6082 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
6091 * Returns true if pressing TAB in this field advances focus instead
6092 * of inserting the character. Insert tabs only in multi-line editors.
6094 private boolean shouldAdvanceFocusOnTab() {
6095 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6096 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6097 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6098 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6099 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6106 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6108 return KEY_EVENT_NOT_HANDLED;
6111 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6112 // While this shouldn't be necessary because any time we're preventing default movement we
6113 // should be restricting the focus to remain within this view, thus we'll also receive
6114 // the key up event, occasionally key up events will get dropped and we don't want to
6115 // prevent the user from traversing out of this on the next key down.
6116 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6117 mPreventDefaultMovement = false;
6121 case KeyEvent.KEYCODE_ENTER:
6122 if (event.hasNoModifiers()) {
6123 // When mInputContentType is set, we know that we are
6124 // running in a "modern" cupcake environment, so don't need
6125 // to worry about the application trying to capture
6126 // enter key events.
6127 if (mEditor != null && mEditor.mInputContentType != null) {
6128 // If there is an action listener, given them a
6129 // chance to consume the event.
6130 if (mEditor.mInputContentType.onEditorActionListener != null &&
6131 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6132 this, EditorInfo.IME_NULL, event)) {
6133 mEditor.mInputContentType.enterDown = true;
6134 // We are consuming the enter key for them.
6135 return KEY_EVENT_HANDLED;
6139 // If our editor should move focus when enter is pressed, or
6140 // this is a generated event from an IME action button, then
6141 // don't let it be inserted into the text.
6142 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6143 || shouldAdvanceFocusOnEnter()) {
6144 if (hasOnClickListeners()) {
6145 return KEY_EVENT_NOT_HANDLED;
6147 return KEY_EVENT_HANDLED;
6152 case KeyEvent.KEYCODE_DPAD_CENTER:
6153 if (event.hasNoModifiers()) {
6154 if (shouldAdvanceFocusOnEnter()) {
6155 return KEY_EVENT_NOT_HANDLED;
6160 case KeyEvent.KEYCODE_TAB:
6161 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6162 if (shouldAdvanceFocusOnTab()) {
6163 return KEY_EVENT_NOT_HANDLED;
6168 // Has to be done on key down (and not on key up) to correctly be intercepted.
6169 case KeyEvent.KEYCODE_BACK:
6170 if (mEditor != null && mEditor.mTextActionMode != null) {
6171 stopTextActionMode();
6172 return KEY_EVENT_HANDLED;
6176 case KeyEvent.KEYCODE_CUT:
6177 if (event.hasNoModifiers() && canCut()) {
6178 if (onTextContextMenuItem(ID_CUT)) {
6179 return KEY_EVENT_HANDLED;
6184 case KeyEvent.KEYCODE_COPY:
6185 if (event.hasNoModifiers() && canCopy()) {
6186 if (onTextContextMenuItem(ID_COPY)) {
6187 return KEY_EVENT_HANDLED;
6192 case KeyEvent.KEYCODE_PASTE:
6193 if (event.hasNoModifiers() && canPaste()) {
6194 if (onTextContextMenuItem(ID_PASTE)) {
6195 return KEY_EVENT_HANDLED;
6201 if (mEditor != null && mEditor.mKeyListener != null) {
6202 boolean doDown = true;
6203 if (otherEvent != null) {
6206 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6208 hideErrorIfUnchanged();
6211 return KEY_EVENT_HANDLED;
6213 } catch (AbstractMethodError e) {
6214 // onKeyOther was added after 1.0, so if it isn't
6215 // implemented we need to try to dispatch as a regular down.
6223 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6226 hideErrorIfUnchanged();
6227 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
6231 // bug 650865: sometimes we get a key event before a layout.
6232 // don't try to move around if we don't know the layout.
6234 if (mMovement != null && mLayout != null) {
6235 boolean doDown = true;
6236 if (otherEvent != null) {
6238 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6242 return KEY_EVENT_HANDLED;
6244 } catch (AbstractMethodError e) {
6245 // onKeyOther was added after 1.0, so if it isn't
6246 // implemented we need to try to dispatch as a regular down.
6250 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6251 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6252 mPreventDefaultMovement = true;
6254 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
6259 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6260 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
6264 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6268 public void resetErrorChangedFlag() {
6270 * Keep track of what the error was before doing the input
6271 * so that if an input filter changed the error, we leave
6272 * that error showing. Otherwise, we take down whatever
6273 * error was showing when the user types something.
6275 if (mEditor != null) mEditor.mErrorWasChanged = false;
6281 public void hideErrorIfUnchanged() {
6282 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6283 setError(null, null);
6288 public boolean onKeyUp(int keyCode, KeyEvent event) {
6290 return super.onKeyUp(keyCode, event);
6293 if (!KeyEvent.isModifierKey(keyCode)) {
6294 mPreventDefaultMovement = false;
6298 case KeyEvent.KEYCODE_DPAD_CENTER:
6299 if (event.hasNoModifiers()) {
6301 * If there is a click listener, just call through to
6302 * super, which will invoke it.
6304 * If there isn't a click listener, try to show the soft
6305 * input method. (It will also
6306 * call performClick(), but that won't do anything in
6309 if (!hasOnClickListeners()) {
6310 if (mMovement != null && mText instanceof Editable
6311 && mLayout != null && onCheckIsTextEditor()) {
6312 InputMethodManager imm = InputMethodManager.peekInstance();
6314 if (imm != null && getShowSoftInputOnFocus()) {
6315 imm.showSoftInput(this, 0);
6320 return super.onKeyUp(keyCode, event);
6322 case KeyEvent.KEYCODE_ENTER:
6323 if (event.hasNoModifiers()) {
6324 if (mEditor != null && mEditor.mInputContentType != null
6325 && mEditor.mInputContentType.onEditorActionListener != null
6326 && mEditor.mInputContentType.enterDown) {
6327 mEditor.mInputContentType.enterDown = false;
6328 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6329 this, EditorInfo.IME_NULL, event)) {
6334 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6335 || shouldAdvanceFocusOnEnter()) {
6337 * If there is a click listener, just call through to
6338 * super, which will invoke it.
6340 * If there isn't a click listener, try to advance focus,
6341 * but still call through to super, which will reset the
6342 * pressed state and longpress state. (It will also
6343 * call performClick(), but that won't do anything in
6346 if (!hasOnClickListeners()) {
6347 View v = focusSearch(FOCUS_DOWN);
6350 if (!v.requestFocus(FOCUS_DOWN)) {
6351 throw new IllegalStateException(
6352 "focus search returned a view " +
6353 "that wasn't able to take focus!");
6357 * Return true because we handled the key; super
6358 * will return false because there was no click
6361 super.onKeyUp(keyCode, event);
6363 } else if ((event.getFlags()
6364 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6365 // No target for next focus, but make sure the IME
6366 // if this came from it.
6367 InputMethodManager imm = InputMethodManager.peekInstance();
6368 if (imm != null && imm.isActive(this)) {
6369 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6374 return super.onKeyUp(keyCode, event);
6379 if (mEditor != null && mEditor.mKeyListener != null)
6380 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6383 if (mMovement != null && mLayout != null)
6384 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6387 return super.onKeyUp(keyCode, event);
6391 public boolean onCheckIsTextEditor() {
6392 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6396 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6397 if (onCheckIsTextEditor() && isEnabled()) {
6398 mEditor.createInputMethodStateIfNeeded();
6399 outAttrs.inputType = getInputType();
6400 if (mEditor.mInputContentType != null) {
6401 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6402 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6403 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6404 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6405 outAttrs.extras = mEditor.mInputContentType.extras;
6407 outAttrs.imeOptions = EditorInfo.IME_NULL;
6409 if (focusSearch(FOCUS_DOWN) != null) {
6410 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6412 if (focusSearch(FOCUS_UP) != null) {
6413 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6415 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6416 == EditorInfo.IME_ACTION_UNSPECIFIED) {
6417 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6418 // An action has not been set, but the enter key will move to
6419 // the next focus, so set the action to that.
6420 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6422 // An action has not been set, and there is no focus to move
6423 // to, so let's just supply a "done" action.
6424 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6426 if (!shouldAdvanceFocusOnEnter()) {
6427 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6430 if (isMultilineInputType(outAttrs.inputType)) {
6431 // Multi-line text editors should always show an enter key.
6432 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6434 outAttrs.hintText = mHint;
6435 if (mText instanceof Editable) {
6436 InputConnection ic = new EditableInputConnection(this);
6437 outAttrs.initialSelStart = getSelectionStart();
6438 outAttrs.initialSelEnd = getSelectionEnd();
6439 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6447 * If this TextView contains editable content, extract a portion of it
6448 * based on the information in <var>request</var> in to <var>outText</var>.
6449 * @return Returns true if the text was successfully extracted, else false.
6451 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6452 createEditorIfNeeded();
6453 return mEditor.extractText(request, outText);
6457 * This is used to remove all style-impacting spans from text before new
6458 * extracted text is being replaced into it, so that we don't have any
6459 * lingering spans applied during the replace.
6461 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6462 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6463 int i = spans.length;
6466 spannable.removeSpan(spans[i]);
6471 * Apply to this text view the given extracted text, as previously
6472 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6474 public void setExtractedText(ExtractedText text) {
6475 Editable content = getEditableText();
6476 if (text.text != null) {
6477 if (content == null) {
6478 setText(text.text, TextView.BufferType.EDITABLE);
6481 int end = content.length();
6483 if (text.partialStartOffset >= 0) {
6484 final int N = content.length();
6485 start = text.partialStartOffset;
6486 if (start > N) start = N;
6487 end = text.partialEndOffset;
6488 if (end > N) end = N;
6491 removeParcelableSpans(content, start, end);
6492 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6493 if (text.text instanceof Spanned) {
6494 // OK to copy spans only.
6495 TextUtils.copySpansFrom((Spanned) text.text, start, end,
6496 Object.class, content, start);
6499 content.replace(start, end, text.text);
6504 // Now set the selection position... make sure it is in range, to
6505 // avoid crashes. If this is a partial update, it is possible that
6506 // the underlying text may have changed, causing us problems here.
6507 // Also we just don't want to trust clients to do the right thing.
6508 Spannable sp = (Spannable)getText();
6509 final int N = sp.length();
6510 int start = text.selectionStart;
6511 if (start < 0) start = 0;
6512 else if (start > N) start = N;
6513 int end = text.selectionEnd;
6514 if (end < 0) end = 0;
6515 else if (end > N) end = N;
6516 Selection.setSelection(sp, start, end);
6518 // Finally, update the selection mode.
6519 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6520 MetaKeyKeyListener.startSelecting(this, sp);
6522 MetaKeyKeyListener.stopSelecting(this, sp);
6529 public void setExtracting(ExtractedTextRequest req) {
6530 if (mEditor.mInputMethodState != null) {
6531 mEditor.mInputMethodState.mExtractedTextRequest = req;
6533 // This would stop a possible selection mode, but no such mode is started in case
6534 // extracted mode will start. Some text is selected though, and will trigger an action mode
6535 // in the extracted view.
6536 mEditor.hideCursorAndSpanControllers();
6537 stopTextActionMode();
6541 * Called by the framework in response to a text completion from
6542 * the current input method, provided by it calling
6543 * {@link InputConnection#commitCompletion
6544 * InputConnection.commitCompletion()}. The default implementation does
6545 * nothing; text views that are supporting auto-completion should override
6546 * this to do their desired behavior.
6548 * @param text The auto complete text the user has selected.
6550 public void onCommitCompletion(CompletionInfo text) {
6551 // intentionally empty
6555 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6556 * a dictionnary) from the current input method, provided by it calling
6557 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6558 * implementation flashes the background of the corrected word to provide feedback to the user.
6560 * @param info The auto correct info about the text that was corrected.
6562 public void onCommitCorrection(CorrectionInfo info) {
6563 if (mEditor != null) mEditor.onCommitCorrection(info);
6566 public void beginBatchEdit() {
6567 if (mEditor != null) mEditor.beginBatchEdit();
6570 public void endBatchEdit() {
6571 if (mEditor != null) mEditor.endBatchEdit();
6575 * Called by the framework in response to a request to begin a batch
6576 * of edit operations through a call to link {@link #beginBatchEdit()}.
6578 public void onBeginBatchEdit() {
6579 // intentionally empty
6583 * Called by the framework in response to a request to end a batch
6584 * of edit operations through a call to link {@link #endBatchEdit}.
6586 public void onEndBatchEdit() {
6587 // intentionally empty
6591 * Called by the framework in response to a private command from the
6592 * current method, provided by it calling
6593 * {@link InputConnection#performPrivateCommand
6594 * InputConnection.performPrivateCommand()}.
6596 * @param action The action name of the command.
6597 * @param data Any additional data for the command. This may be null.
6598 * @return Return true if you handled the command, else false.
6600 public boolean onPrivateIMECommand(String action, Bundle data) {
6604 private void nullLayouts() {
6605 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6606 mSavedLayout = (BoringLayout) mLayout;
6608 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6609 mSavedHintLayout = (BoringLayout) mHintLayout;
6612 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6614 mBoring = mHintBoring = null;
6616 // Since it depends on the value of mLayout
6617 if (mEditor != null) mEditor.prepareCursorControllers();
6621 * Make a new Layout based on the already-measured size of the view,
6622 * on the assumption that it was measured correctly at some point.
6624 private void assumeLayout() {
6625 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6631 int physicalWidth = width;
6633 if (mHorizontallyScrolling) {
6637 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6638 physicalWidth, false);
6641 private Layout.Alignment getLayoutAlignment() {
6642 Layout.Alignment alignment;
6643 switch (getTextAlignment()) {
6644 case TEXT_ALIGNMENT_GRAVITY:
6645 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6647 alignment = Layout.Alignment.ALIGN_NORMAL;
6650 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6653 alignment = Layout.Alignment.ALIGN_LEFT;
6656 alignment = Layout.Alignment.ALIGN_RIGHT;
6658 case Gravity.CENTER_HORIZONTAL:
6659 alignment = Layout.Alignment.ALIGN_CENTER;
6662 alignment = Layout.Alignment.ALIGN_NORMAL;
6666 case TEXT_ALIGNMENT_TEXT_START:
6667 alignment = Layout.Alignment.ALIGN_NORMAL;
6669 case TEXT_ALIGNMENT_TEXT_END:
6670 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6672 case TEXT_ALIGNMENT_CENTER:
6673 alignment = Layout.Alignment.ALIGN_CENTER;
6675 case TEXT_ALIGNMENT_VIEW_START:
6676 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6677 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6679 case TEXT_ALIGNMENT_VIEW_END:
6680 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6681 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6683 case TEXT_ALIGNMENT_INHERIT:
6684 // This should never happen as we have already resolved the text alignment
6685 // but better safe than sorry so we just fall through
6687 alignment = Layout.Alignment.ALIGN_NORMAL;
6694 * The width passed in is now the desired layout width,
6695 * not the full view width with padding.
6698 protected void makeNewLayout(int wantWidth, int hintWidth,
6699 BoringLayout.Metrics boring,
6700 BoringLayout.Metrics hintBoring,
6701 int ellipsisWidth, boolean bringIntoView) {
6704 // Update "old" cached values
6705 mOldMaximum = mMaximum;
6706 mOldMaxMode = mMaxMode;
6708 mHighlightPathBogus = true;
6710 if (wantWidth < 0) {
6713 if (hintWidth < 0) {
6717 Layout.Alignment alignment = getLayoutAlignment();
6718 final boolean testDirChange = mSingleLine && mLayout != null &&
6719 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6720 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6722 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6723 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6724 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6725 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6726 TruncateAt effectiveEllipsize = mEllipsize;
6727 if (mEllipsize == TruncateAt.MARQUEE &&
6728 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6729 effectiveEllipsize = TruncateAt.END_SMALL;
6732 if (mTextDir == null) {
6733 mTextDir = getTextDirectionHeuristic();
6736 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6737 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6738 if (switchEllipsize) {
6739 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6740 TruncateAt.END : TruncateAt.MARQUEE;
6741 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6742 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6745 shouldEllipsize = mEllipsize != null;
6748 if (mHint != null) {
6749 if (shouldEllipsize) hintWidth = wantWidth;
6751 if (hintBoring == UNKNOWN_BORING) {
6752 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6754 if (hintBoring != null) {
6755 mHintBoring = hintBoring;
6759 if (hintBoring != null) {
6760 if (hintBoring.width <= hintWidth &&
6761 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6762 if (mSavedHintLayout != null) {
6763 mHintLayout = mSavedHintLayout.
6764 replaceOrMake(mHint, mTextPaint,
6765 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6766 hintBoring, mIncludePad);
6768 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6769 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6770 hintBoring, mIncludePad);
6773 mSavedHintLayout = (BoringLayout) mHintLayout;
6774 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6775 if (mSavedHintLayout != null) {
6776 mHintLayout = mSavedHintLayout.
6777 replaceOrMake(mHint, mTextPaint,
6778 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6779 hintBoring, mIncludePad, mEllipsize,
6782 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6783 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6784 hintBoring, mIncludePad, mEllipsize,
6789 // TODO: code duplication with makeSingleLayout()
6790 if (mHintLayout == null) {
6791 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6792 mHint.length(), mTextPaint, hintWidth)
6793 .setAlignment(alignment)
6794 .setTextDirection(mTextDir)
6795 .setLineSpacing(mSpacingAdd, mSpacingMult)
6796 .setIncludePad(mIncludePad)
6797 .setBreakStrategy(mBreakStrategy)
6798 .setHyphenationFrequency(mHyphenationFrequency);
6799 if (shouldEllipsize) {
6800 builder.setEllipsize(mEllipsize)
6801 .setEllipsizedWidth(ellipsisWidth)
6802 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6804 mHintLayout = builder.build();
6808 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6809 registerForPreDraw();
6812 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6813 if (!compressText(ellipsisWidth)) {
6814 // If the size of the view does not depend on the size of the text, try to
6815 // start the marquee immediately
6816 final ViewParent parent = getParent();
6817 if (parent != null && parent.findDependentLayoutAxes(this,
6818 ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) {
6821 // Defer the start of the marquee until we know our width (see setFrame())
6822 mRestartMarquee = true;
6827 // CursorControllers need a non-null mLayout
6828 if (mEditor != null) mEditor.prepareCursorControllers();
6831 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6832 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6834 Layout result = null;
6835 if (mText instanceof Spannable) {
6836 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6837 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6838 mBreakStrategy, mHyphenationFrequency,
6839 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6841 if (boring == UNKNOWN_BORING) {
6842 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6843 if (boring != null) {
6848 if (boring != null) {
6849 if (boring.width <= wantWidth &&
6850 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6851 if (useSaved && mSavedLayout != null) {
6852 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6853 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6854 boring, mIncludePad);
6856 result = BoringLayout.make(mTransformed, mTextPaint,
6857 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6858 boring, mIncludePad);
6862 mSavedLayout = (BoringLayout) result;
6864 } else if (shouldEllipsize && boring.width <= wantWidth) {
6865 if (useSaved && mSavedLayout != null) {
6866 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6867 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6868 boring, mIncludePad, effectiveEllipsize,
6871 result = BoringLayout.make(mTransformed, mTextPaint,
6872 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6873 boring, mIncludePad, effectiveEllipsize,
6879 if (result == null) {
6880 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6881 0, mTransformed.length(), mTextPaint, wantWidth)
6882 .setAlignment(alignment)
6883 .setTextDirection(mTextDir)
6884 .setLineSpacing(mSpacingAdd, mSpacingMult)
6885 .setIncludePad(mIncludePad)
6886 .setBreakStrategy(mBreakStrategy)
6887 .setHyphenationFrequency(mHyphenationFrequency);
6888 if (shouldEllipsize) {
6889 builder.setEllipsize(effectiveEllipsize)
6890 .setEllipsizedWidth(ellipsisWidth)
6891 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6893 // TODO: explore always setting maxLines
6894 result = builder.build();
6899 private boolean compressText(float width) {
6900 if (isHardwareAccelerated()) return false;
6902 // Only compress the text if it hasn't been compressed by the previous pass
6903 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6904 mTextPaint.getTextScaleX() == 1.0f) {
6905 final float textWidth = mLayout.getLineWidth(0);
6906 final float overflow = (textWidth + 1.0f - width) / width;
6907 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6908 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6909 post(new Runnable() {
6921 private static int desired(Layout layout) {
6922 int n = layout.getLineCount();
6923 CharSequence text = layout.getText();
6926 // if any line was wrapped, we can't use it.
6927 // but it's ok for the last line not to have a newline
6929 for (int i = 0; i < n - 1; i++) {
6930 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6934 for (int i = 0; i < n; i++) {
6935 max = Math.max(max, layout.getLineWidth(i));
6938 return (int) Math.ceil(max);
6942 * Set whether the TextView includes extra top and bottom padding to make
6943 * room for accents that go above the normal ascent and descent.
6944 * The default is true.
6946 * @see #getIncludeFontPadding()
6948 * @attr ref android.R.styleable#TextView_includeFontPadding
6950 public void setIncludeFontPadding(boolean includepad) {
6951 if (mIncludePad != includepad) {
6952 mIncludePad = includepad;
6954 if (mLayout != null) {
6963 * Gets whether the TextView includes extra top and bottom padding to make
6964 * room for accents that go above the normal ascent and descent.
6966 * @see #setIncludeFontPadding(boolean)
6968 * @attr ref android.R.styleable#TextView_includeFontPadding
6970 public boolean getIncludeFontPadding() {
6974 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6977 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6978 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6979 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6980 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6981 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6986 BoringLayout.Metrics boring = UNKNOWN_BORING;
6987 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6989 if (mTextDir == null) {
6990 mTextDir = getTextDirectionHeuristic();
6994 boolean fromexisting = false;
6996 if (widthMode == MeasureSpec.EXACTLY) {
6997 // Parent has told us how big to be. So be it.
7000 if (mLayout != null && mEllipsize == null) {
7001 des = desired(mLayout);
7005 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7006 if (boring != null) {
7010 fromexisting = true;
7013 if (boring == null || boring == UNKNOWN_BORING) {
7015 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
7019 width = boring.width;
7022 final Drawables dr = mDrawables;
7024 width = Math.max(width, dr.mDrawableWidthTop);
7025 width = Math.max(width, dr.mDrawableWidthBottom);
7028 if (mHint != null) {
7032 if (mHintLayout != null && mEllipsize == null) {
7033 hintDes = desired(mHintLayout);
7037 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
7038 if (hintBoring != null) {
7039 mHintBoring = hintBoring;
7043 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7045 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
7047 hintWidth = hintDes;
7049 hintWidth = hintBoring.width;
7052 if (hintWidth > width) {
7057 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7059 if (mMaxWidthMode == EMS) {
7060 width = Math.min(width, mMaxWidth * getLineHeight());
7062 width = Math.min(width, mMaxWidth);
7065 if (mMinWidthMode == EMS) {
7066 width = Math.max(width, mMinWidth * getLineHeight());
7068 width = Math.max(width, mMinWidth);
7071 // Check against our minimum width
7072 width = Math.max(width, getSuggestedMinimumWidth());
7074 if (widthMode == MeasureSpec.AT_MOST) {
7075 width = Math.min(widthSize, width);
7079 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7080 int unpaddedWidth = want;
7082 if (mHorizontallyScrolling) want = VERY_WIDE;
7084 int hintWant = want;
7085 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
7087 if (mLayout == null) {
7088 makeNewLayout(want, hintWant, boring, hintBoring,
7089 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7091 final boolean layoutChanged = (mLayout.getWidth() != want) ||
7092 (hintWidth != hintWant) ||
7093 (mLayout.getEllipsizedWidth() !=
7094 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7096 final boolean widthChanged = (mHint == null) &&
7097 (mEllipsize == null) &&
7098 (want > mLayout.getWidth()) &&
7099 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7101 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7103 if (layoutChanged || maximumChanged) {
7104 if (!maximumChanged && widthChanged) {
7105 mLayout.increaseWidthTo(want);
7107 makeNewLayout(want, hintWant, boring, hintBoring,
7108 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7111 // Nothing has changed
7115 if (heightMode == MeasureSpec.EXACTLY) {
7116 // Parent has told us how big to be. So be it.
7117 height = heightSize;
7118 mDesiredHeightAtMeasure = -1;
7120 int desired = getDesiredHeight();
7123 mDesiredHeightAtMeasure = desired;
7125 if (heightMode == MeasureSpec.AT_MOST) {
7126 height = Math.min(desired, heightSize);
7130 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
7131 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
7132 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
7136 * We didn't let makeNewLayout() register to bring the cursor into view,
7137 * so do it here if there is any possibility that it is needed.
7139 if (mMovement != null ||
7140 mLayout.getWidth() > unpaddedWidth ||
7141 mLayout.getHeight() > unpaddedHeight) {
7142 registerForPreDraw();
7147 setMeasuredDimension(width, height);
7150 private int getDesiredHeight() {
7152 getDesiredHeight(mLayout, true),
7153 getDesiredHeight(mHintLayout, mEllipsize != null));
7156 private int getDesiredHeight(Layout layout, boolean cap) {
7157 if (layout == null) {
7161 int linecount = layout.getLineCount();
7162 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7163 int desired = layout.getLineTop(linecount);
7165 final Drawables dr = mDrawables;
7167 desired = Math.max(desired, dr.mDrawableHeightLeft);
7168 desired = Math.max(desired, dr.mDrawableHeightRight);
7173 if (mMaxMode == LINES) {
7175 * Don't cap the hint to a certain number of lines.
7176 * (Do cap it, though, if we have a maximum pixel height.)
7179 if (linecount > mMaximum) {
7180 desired = layout.getLineTop(mMaximum);
7183 desired = Math.max(desired, dr.mDrawableHeightLeft);
7184 desired = Math.max(desired, dr.mDrawableHeightRight);
7188 linecount = mMaximum;
7192 desired = Math.min(desired, mMaximum);
7195 if (mMinMode == LINES) {
7196 if (linecount < mMinimum) {
7197 desired += getLineHeight() * (mMinimum - linecount);
7200 desired = Math.max(desired, mMinimum);
7203 // Check against our minimum height
7204 desired = Math.max(desired, getSuggestedMinimumHeight());
7210 * Check whether a change to the existing text layout requires a
7213 private void checkForResize() {
7214 // Always request a layout. The parent will perform the correct version
7215 // of the intended optimizations as part of requestLayoutForChild.
7220 * Check whether entirely new text requires a new view layout
7221 * or merely a new text layout.
7223 private void checkForRelayout() {
7224 // Always request a layout. The parent will perform the correct version
7225 // of the intended optimizations as part of requestLayoutForChild.
7231 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7232 super.onLayout(changed, left, top, right, bottom);
7233 if (mDeferScroll >= 0) {
7234 int curs = mDeferScroll;
7236 bringPointIntoView(Math.min(curs, mText.length()));
7240 private boolean isShowingHint() {
7241 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7245 * Returns true if anything changed.
7247 private boolean bringTextIntoView() {
7248 Layout layout = isShowingHint() ? mHintLayout : mLayout;
7250 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7251 line = layout.getLineCount() - 1;
7254 Layout.Alignment a = layout.getParagraphAlignment(line);
7255 int dir = layout.getParagraphDirection(line);
7256 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7257 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7258 int ht = layout.getHeight();
7260 int scrollx, scrolly;
7262 // Convert to left, center, or right alignment.
7263 if (a == Layout.Alignment.ALIGN_NORMAL) {
7264 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7265 Layout.Alignment.ALIGN_RIGHT;
7266 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7267 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7268 Layout.Alignment.ALIGN_LEFT;
7271 if (a == Layout.Alignment.ALIGN_CENTER) {
7273 * Keep centered if possible, or, if it is too wide to fit,
7274 * keep leading edge in view.
7277 int left = (int) Math.floor(layout.getLineLeft(line));
7278 int right = (int) Math.ceil(layout.getLineRight(line));
7280 if (right - left < hspace) {
7281 scrollx = (right + left) / 2 - hspace / 2;
7284 scrollx = right - hspace;
7289 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7290 int right = (int) Math.ceil(layout.getLineRight(line));
7291 scrollx = right - hspace;
7292 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7293 scrollx = (int) Math.floor(layout.getLineLeft(line));
7299 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7300 scrolly = ht - vspace;
7306 if (scrollx != mScrollX || scrolly != mScrollY) {
7307 scrollTo(scrollx, scrolly);
7315 * Move the point, specified by the offset, into the view if it is needed.
7316 * This has to be called after layout. Returns true if anything changed.
7318 public boolean bringPointIntoView(int offset) {
7319 if (isLayoutRequested()) {
7320 mDeferScroll = offset;
7323 boolean changed = false;
7325 Layout layout = isShowingHint() ? mHintLayout: mLayout;
7327 if (layout == null) return changed;
7329 int line = layout.getLineForOffset(offset);
7333 switch (layout.getParagraphAlignment(line)) {
7341 grav = layout.getParagraphDirection(line);
7343 case ALIGN_OPPOSITE:
7344 grav = -layout.getParagraphDirection(line);
7352 // We only want to clamp the cursor to fit within the layout width
7353 // in left-to-right modes, because in a right to left alignment,
7354 // we want to scroll to keep the line-right on the screen, as other
7355 // lines are likely to have text flush with the right margin, which
7356 // we want to keep visible.
7357 // A better long-term solution would probably be to measure both
7358 // the full line and a blank-trimmed version, and, for example, use
7359 // the latter measurement for centering and right alignment, but for
7360 // the time being we only implement the cursor clamping in left to
7361 // right where it is most likely to be annoying.
7362 final boolean clamped = grav > 0;
7363 // FIXME: Is it okay to truncate this, or should we round?
7364 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7365 final int top = layout.getLineTop(line);
7366 final int bottom = layout.getLineTop(line + 1);
7368 int left = (int) Math.floor(layout.getLineLeft(line));
7369 int right = (int) Math.ceil(layout.getLineRight(line));
7370 int ht = layout.getHeight();
7372 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7373 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7374 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7375 // If cursor has been clamped, make sure we don't scroll.
7376 right = Math.max(x, left + hspace);
7379 int hslack = (bottom - top) / 2;
7380 int vslack = hslack;
7382 if (vslack > vspace / 4)
7383 vslack = vspace / 4;
7384 if (hslack > hspace / 4)
7385 hslack = hspace / 4;
7390 if (top - vs < vslack)
7392 if (bottom - vs > vspace - vslack)
7393 vs = bottom - (vspace - vslack);
7394 if (ht - vs < vspace)
7400 if (x - hs < hslack) {
7403 if (x - hs > hspace - hslack) {
7404 hs = x - (hspace - hslack);
7411 if (right - hs < hspace)
7412 hs = right - hspace;
7413 } else if (grav > 0) {
7414 if (right - hs < hspace)
7415 hs = right - hspace;
7418 } else /* grav == 0 */ {
7419 if (right - left <= hspace) {
7421 * If the entire text fits, center it exactly.
7423 hs = left - (hspace - (right - left)) / 2;
7424 } else if (x > right - hslack) {
7426 * If we are near the right edge, keep the right edge
7427 * at the edge of the view.
7429 hs = right - hspace;
7430 } else if (x < left + hslack) {
7432 * If we are near the left edge, keep the left edge
7433 * at the edge of the view.
7436 } else if (left > hs) {
7438 * Is there whitespace visible at the left? Fix it if so.
7441 } else if (right < hs + hspace) {
7443 * Is there whitespace visible at the right? Fix it if so.
7445 hs = right - hspace;
7448 * Otherwise, float as needed.
7450 if (x - hs < hslack) {
7453 if (x - hs > hspace - hslack) {
7454 hs = x - (hspace - hslack);
7459 if (hs != mScrollX || vs != mScrollY) {
7460 if (mScroller == null) {
7463 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7464 int dx = hs - mScrollX;
7465 int dy = vs - mScrollY;
7467 if (duration > ANIMATED_SCROLL_GAP) {
7468 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7469 awakenScrollBars(mScroller.getDuration());
7472 if (!mScroller.isFinished()) {
7473 mScroller.abortAnimation();
7479 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7486 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7487 // requestRectangleOnScreen() is in terms of content coordinates.
7489 // The offsets here are to ensure the rectangle we are using is
7490 // within our view bounds, in case the cursor is on the far left
7491 // or right. If it isn't withing the bounds, then this request
7493 if (mTempRect == null) mTempRect = new Rect();
7494 mTempRect.set(x - 2, top, x + 2, bottom);
7495 getInterestingRect(mTempRect, line);
7496 mTempRect.offset(mScrollX, mScrollY);
7498 if (requestRectangleOnScreen(mTempRect)) {
7507 * Move the cursor, if needed, so that it is at an offset that is visible
7508 * to the user. This will not move the cursor if it represents more than
7509 * one character (a selection range). This will only work if the
7510 * TextView contains spannable text; otherwise it will do nothing.
7512 * @return True if the cursor was actually moved, false otherwise.
7514 public boolean moveCursorToVisibleOffset() {
7515 if (!(mText instanceof Spannable)) {
7518 int start = getSelectionStart();
7519 int end = getSelectionEnd();
7524 // First: make sure the line is visible on screen:
7526 int line = mLayout.getLineForOffset(start);
7528 final int top = mLayout.getLineTop(line);
7529 final int bottom = mLayout.getLineTop(line + 1);
7530 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7531 int vslack = (bottom - top) / 2;
7532 if (vslack > vspace / 4)
7533 vslack = vspace / 4;
7534 final int vs = mScrollY;
7536 if (top < (vs+vslack)) {
7537 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7538 } else if (bottom > (vspace+vs-vslack)) {
7539 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7542 // Next: make sure the character is visible on screen:
7544 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7545 final int hs = mScrollX;
7546 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7547 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7549 // line might contain bidirectional text
7550 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7551 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7553 int newStart = start;
7554 if (newStart < lowChar) {
7556 } else if (newStart > highChar) {
7557 newStart = highChar;
7560 if (newStart != start) {
7561 Selection.setSelection((Spannable)mText, newStart);
7569 public void computeScroll() {
7570 if (mScroller != null) {
7571 if (mScroller.computeScrollOffset()) {
7572 mScrollX = mScroller.getCurrX();
7573 mScrollY = mScroller.getCurrY();
7574 invalidateParentCaches();
7575 postInvalidate(); // So we draw again
7580 private void getInterestingRect(Rect r, int line) {
7581 convertFromViewportToContentCoordinates(r);
7583 // Rectangle can can be expanded on first and last line to take
7584 // padding into account.
7585 // TODO Take left/right padding into account too?
7586 if (line == 0) r.top -= getExtendedPaddingTop();
7587 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7590 private void convertFromViewportToContentCoordinates(Rect r) {
7591 final int horizontalOffset = viewportToContentHorizontalOffset();
7592 r.left += horizontalOffset;
7593 r.right += horizontalOffset;
7595 final int verticalOffset = viewportToContentVerticalOffset();
7596 r.top += verticalOffset;
7597 r.bottom += verticalOffset;
7600 int viewportToContentHorizontalOffset() {
7601 return getCompoundPaddingLeft() - mScrollX;
7604 int viewportToContentVerticalOffset() {
7605 int offset = getExtendedPaddingTop() - mScrollY;
7606 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7607 offset += getVerticalOffset(false);
7613 public void debug(int depth) {
7616 String output = debugIndent(depth);
7617 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7618 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7621 if (mText != null) {
7623 output += "mText=\"" + mText + "\" ";
7624 if (mLayout != null) {
7625 output += "mLayout width=" + mLayout.getWidth()
7626 + " height=" + mLayout.getHeight();
7629 output += "mText=NULL";
7631 Log.d(VIEW_LOG_TAG, output);
7635 * Convenience for {@link Selection#getSelectionStart}.
7637 @ViewDebug.ExportedProperty(category = "text")
7638 public int getSelectionStart() {
7639 return Selection.getSelectionStart(getText());
7643 * Convenience for {@link Selection#getSelectionEnd}.
7645 @ViewDebug.ExportedProperty(category = "text")
7646 public int getSelectionEnd() {
7647 return Selection.getSelectionEnd(getText());
7651 * Return true iff there is a selection inside this text view.
7653 public boolean hasSelection() {
7654 final int selectionStart = getSelectionStart();
7655 final int selectionEnd = getSelectionEnd();
7657 return selectionStart >= 0 && selectionStart != selectionEnd;
7660 String getSelectedText() {
7661 if (!hasSelection()) {
7665 final int start = getSelectionStart();
7666 final int end = getSelectionEnd();
7667 return String.valueOf(
7668 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
7672 * Sets the properties of this field (lines, horizontally scrolling,
7673 * transformation method) to be for a single-line input.
7675 * @attr ref android.R.styleable#TextView_singleLine
7677 public void setSingleLine() {
7678 setSingleLine(true);
7682 * Sets the properties of this field to transform input to ALL CAPS
7683 * display. This may use a "small caps" formatting if available.
7684 * This setting will be ignored if this field is editable or selectable.
7686 * This call replaces the current transformation method. Disabling this
7687 * will not necessarily restore the previous behavior from before this
7690 * @see #setTransformationMethod(TransformationMethod)
7691 * @attr ref android.R.styleable#TextView_textAllCaps
7693 public void setAllCaps(boolean allCaps) {
7695 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7697 setTransformationMethod(null);
7702 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7703 * transformation method) to be for a single-line input; if false, restores these to the default
7706 * Note that the default conditions are not necessarily those that were in effect prior this
7707 * method, and you may want to reset these properties to your custom values.
7709 * @attr ref android.R.styleable#TextView_singleLine
7711 @android.view.RemotableViewMethod
7712 public void setSingleLine(boolean singleLine) {
7713 // Could be used, but may break backward compatibility.
7714 // if (mSingleLine == singleLine) return;
7715 setInputTypeSingleLine(singleLine);
7716 applySingleLine(singleLine, true, true);
7720 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7723 private void setInputTypeSingleLine(boolean singleLine) {
7724 if (mEditor != null &&
7725 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7727 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7729 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7734 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7735 boolean changeMaxLines) {
7736 mSingleLine = singleLine;
7739 setHorizontallyScrolling(true);
7740 if (applyTransformation) {
7741 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7744 if (changeMaxLines) {
7745 setMaxLines(Integer.MAX_VALUE);
7747 setHorizontallyScrolling(false);
7748 if (applyTransformation) {
7749 setTransformationMethod(null);
7755 * Causes words in the text that are longer than the view is wide
7756 * to be ellipsized instead of broken in the middle. You may also
7757 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7758 * to constrain the text to a single line. Use <code>null</code>
7759 * to turn off ellipsizing.
7761 * If {@link #setMaxLines} has been used to set two or more lines,
7762 * only {@link android.text.TextUtils.TruncateAt#END} and
7763 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7764 * (other ellipsizing types will not do anything).
7766 * @attr ref android.R.styleable#TextView_ellipsize
7768 public void setEllipsize(TextUtils.TruncateAt where) {
7769 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7770 if (mEllipsize != where) {
7773 if (mLayout != null) {
7782 * Sets how many times to repeat the marquee animation. Only applied if the
7783 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7785 * @see #getMarqueeRepeatLimit()
7787 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7789 public void setMarqueeRepeatLimit(int marqueeLimit) {
7790 mMarqueeRepeatLimit = marqueeLimit;
7794 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7795 * TextView has marquee enabled.
7797 * @return the number of times the marquee animation is repeated. -1 if the animation
7798 * repeats indefinitely
7800 * @see #setMarqueeRepeatLimit(int)
7802 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7804 public int getMarqueeRepeatLimit() {
7805 return mMarqueeRepeatLimit;
7809 * Returns where, if anywhere, words that are longer than the view
7810 * is wide should be ellipsized.
7812 @ViewDebug.ExportedProperty
7813 public TextUtils.TruncateAt getEllipsize() {
7818 * Set the TextView so that when it takes focus, all the text is
7821 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7823 @android.view.RemotableViewMethod
7824 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7825 createEditorIfNeeded();
7826 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7828 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7829 setText(mText, BufferType.SPANNABLE);
7834 * Set whether the cursor is visible. The default is true. Note that this property only
7835 * makes sense for editable TextView.
7837 * @see #isCursorVisible()
7839 * @attr ref android.R.styleable#TextView_cursorVisible
7841 @android.view.RemotableViewMethod
7842 public void setCursorVisible(boolean visible) {
7843 if (visible && mEditor == null) return; // visible is the default value with no edit data
7844 createEditorIfNeeded();
7845 if (mEditor.mCursorVisible != visible) {
7846 mEditor.mCursorVisible = visible;
7849 mEditor.makeBlink();
7851 // InsertionPointCursorController depends on mCursorVisible
7852 mEditor.prepareCursorControllers();
7857 * @return whether or not the cursor is visible (assuming this TextView is editable)
7859 * @see #setCursorVisible(boolean)
7861 * @attr ref android.R.styleable#TextView_cursorVisible
7863 public boolean isCursorVisible() {
7864 // true is the default value
7865 return mEditor == null ? true : mEditor.mCursorVisible;
7868 private boolean canMarquee() {
7869 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7870 return width > 0 && (mLayout.getLineWidth(0) > width ||
7871 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7872 mSavedMarqueeModeLayout.getLineWidth(0) > width));
7875 private void startMarquee() {
7876 // Do not ellipsize EditText
7877 if (getKeyListener() != null) return;
7879 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7883 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7884 getLineCount() == 1 && canMarquee()) {
7886 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7887 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7888 final Layout tmp = mLayout;
7889 mLayout = mSavedMarqueeModeLayout;
7890 mSavedMarqueeModeLayout = tmp;
7891 setHorizontalFadingEdgeEnabled(true);
7896 if (mMarquee == null) mMarquee = new Marquee(this);
7897 mMarquee.start(mMarqueeRepeatLimit);
7901 private void stopMarquee() {
7902 if (mMarquee != null && !mMarquee.isStopped()) {
7906 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7907 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7908 final Layout tmp = mSavedMarqueeModeLayout;
7909 mSavedMarqueeModeLayout = mLayout;
7911 setHorizontalFadingEdgeEnabled(false);
7917 private void startStopMarquee(boolean start) {
7918 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7928 * This method is called when the text is changed, in case any subclasses
7929 * would like to know.
7931 * Within <code>text</code>, the <code>lengthAfter</code> characters
7932 * beginning at <code>start</code> have just replaced old text that had
7933 * length <code>lengthBefore</code>. It is an error to attempt to make
7934 * changes to <code>text</code> from this callback.
7936 * @param text The text the TextView is displaying
7937 * @param start The offset of the start of the range of the text that was
7939 * @param lengthBefore The length of the former text that has been replaced
7940 * @param lengthAfter The length of the replacement modified text
7942 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7943 // intentionally empty, template pattern method can be overridden by subclasses
7947 * This method is called when the selection has changed, in case any
7948 * subclasses would like to know.
7950 * @param selStart The new selection start location.
7951 * @param selEnd The new selection end location.
7953 protected void onSelectionChanged(int selStart, int selEnd) {
7954 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7958 * Adds a TextWatcher to the list of those whose methods are called
7959 * whenever this TextView's text changes.
7961 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7962 * not called after {@link #setText} calls. Now, doing {@link #setText}
7963 * if there are any text changed listeners forces the buffer type to
7964 * Editable if it would not otherwise be and does call this method.
7966 public void addTextChangedListener(TextWatcher watcher) {
7967 if (mListeners == null) {
7968 mListeners = new ArrayList<TextWatcher>();
7971 mListeners.add(watcher);
7975 * Removes the specified TextWatcher from the list of those whose
7976 * methods are called
7977 * whenever this TextView's text changes.
7979 public void removeTextChangedListener(TextWatcher watcher) {
7980 if (mListeners != null) {
7981 int i = mListeners.indexOf(watcher);
7984 mListeners.remove(i);
7989 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7990 if (mListeners != null) {
7991 final ArrayList<TextWatcher> list = mListeners;
7992 final int count = list.size();
7993 for (int i = 0; i < count; i++) {
7994 list.get(i).beforeTextChanged(text, start, before, after);
7998 // The spans that are inside or intersect the modified region no longer make sense
7999 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8000 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
8003 // Removes all spans that are inside or actually overlap the start..end range
8004 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
8005 if (!(mText instanceof Editable)) return;
8006 Editable text = (Editable) mText;
8008 T[] spans = text.getSpans(start, end, type);
8009 final int length = spans.length;
8010 for (int i = 0; i < length; i++) {
8011 final int spanStart = text.getSpanStart(spans[i]);
8012 final int spanEnd = text.getSpanEnd(spans[i]);
8013 if (spanEnd == start || spanStart == end) break;
8014 text.removeSpan(spans[i]);
8018 void removeAdjacentSuggestionSpans(final int pos) {
8019 if (!(mText instanceof Editable)) return;
8020 final Editable text = (Editable) mText;
8022 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8023 final int length = spans.length;
8024 for (int i = 0; i < length; i++) {
8025 final int spanStart = text.getSpanStart(spans[i]);
8026 final int spanEnd = text.getSpanEnd(spans[i]);
8027 if (spanEnd == pos || spanStart == pos) {
8028 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8029 text.removeSpan(spans[i]);
8036 * Not private so it can be called from an inner class without going
8039 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
8040 if (mListeners != null) {
8041 final ArrayList<TextWatcher> list = mListeners;
8042 final int count = list.size();
8043 for (int i = 0; i < count; i++) {
8044 list.get(i).onTextChanged(text, start, before, after);
8048 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
8052 * Not private so it can be called from an inner class without going
8055 void sendAfterTextChanged(Editable text) {
8056 if (mListeners != null) {
8057 final ArrayList<TextWatcher> list = mListeners;
8058 final int count = list.size();
8059 for (int i = 0; i < count; i++) {
8060 list.get(i).afterTextChanged(text);
8063 hideErrorIfUnchanged();
8066 void updateAfterEdit() {
8068 int curs = getSelectionStart();
8070 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8071 registerForPreDraw();
8077 mHighlightPathBogus = true;
8078 if (mEditor != null) mEditor.makeBlink();
8079 bringPointIntoView(curs);
8084 * Not private so it can be called from an inner class without going
8087 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8088 sLastCutCopyOrTextChangedTime = 0;
8090 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8091 if (ims == null || ims.mBatchEditNesting == 0) {
8095 ims.mContentChanged = true;
8096 if (ims.mChangedStart < 0) {
8097 ims.mChangedStart = start;
8098 ims.mChangedEnd = start+before;
8100 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8101 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8103 ims.mChangedDelta += after-before;
8105 resetErrorChangedFlag();
8106 sendOnTextChanged(buffer, start, before, after);
8107 onTextChanged(buffer, start, before, after);
8111 * Not private so it can be called from an inner class without going
8114 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8115 // XXX Make the start and end move together if this ends up
8116 // spending too much time invalidating.
8118 boolean selChanged = false;
8119 int newSelStart=-1, newSelEnd=-1;
8121 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8123 if (what == Selection.SELECTION_END) {
8125 newSelEnd = newStart;
8127 if (oldStart >= 0 || newStart >= 0) {
8128 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8130 registerForPreDraw();
8131 if (mEditor != null) mEditor.makeBlink();
8135 if (what == Selection.SELECTION_START) {
8137 newSelStart = newStart;
8139 if (oldStart >= 0 || newStart >= 0) {
8140 int end = Selection.getSelectionEnd(buf);
8141 invalidateCursor(end, oldStart, newStart);
8146 mHighlightPathBogus = true;
8147 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8149 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8150 if (newSelStart < 0) {
8151 newSelStart = Selection.getSelectionStart(buf);
8153 if (newSelEnd < 0) {
8154 newSelEnd = Selection.getSelectionEnd(buf);
8156 onSelectionChanged(newSelStart, newSelEnd);
8160 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8161 what instanceof CharacterStyle) {
8162 if (ims == null || ims.mBatchEditNesting == 0) {
8164 mHighlightPathBogus = true;
8167 ims.mContentChanged = true;
8169 if (mEditor != null) {
8170 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8171 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8175 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8176 mHighlightPathBogus = true;
8177 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8178 ims.mSelectionModeChanged = true;
8181 if (Selection.getSelectionStart(buf) >= 0) {
8182 if (ims == null || ims.mBatchEditNesting == 0) {
8185 ims.mCursorChanged = true;
8190 if (what instanceof ParcelableSpan) {
8191 // If this is a span that can be sent to a remote process,
8192 // the current extract editor would be interested in it.
8193 if (ims != null && ims.mExtractedTextRequest != null) {
8194 if (ims.mBatchEditNesting != 0) {
8195 if (oldStart >= 0) {
8196 if (ims.mChangedStart > oldStart) {
8197 ims.mChangedStart = oldStart;
8199 if (ims.mChangedStart > oldEnd) {
8200 ims.mChangedStart = oldEnd;
8203 if (newStart >= 0) {
8204 if (ims.mChangedStart > newStart) {
8205 ims.mChangedStart = newStart;
8207 if (ims.mChangedStart > newEnd) {
8208 ims.mChangedStart = newEnd;
8212 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8213 + oldStart + "-" + oldEnd + ","
8214 + newStart + "-" + newEnd + " " + what);
8215 ims.mContentChanged = true;
8220 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8221 what instanceof SpellCheckSpan) {
8222 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8230 public void dispatchFinishTemporaryDetach() {
8231 mDispatchTemporaryDetach = true;
8232 super.dispatchFinishTemporaryDetach();
8233 mDispatchTemporaryDetach = false;
8237 public void onStartTemporaryDetach() {
8238 super.onStartTemporaryDetach();
8239 // Only track when onStartTemporaryDetach() is called directly,
8240 // usually because this instance is an editable field in a list
8241 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8243 // Tell the editor that we are temporarily detached. It can use this to preserve
8244 // selection state as needed.
8245 if (mEditor != null) mEditor.mTemporaryDetach = true;
8249 public void onFinishTemporaryDetach() {
8250 super.onFinishTemporaryDetach();
8251 // Only track when onStartTemporaryDetach() is called directly,
8252 // usually because this instance is an editable field in a list
8253 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8254 if (mEditor != null) mEditor.mTemporaryDetach = false;
8258 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8259 if (mTemporaryDetach) {
8260 // If we are temporarily in the detach state, then do nothing.
8261 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8265 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8268 if (mText instanceof Spannable) {
8269 Spannable sp = (Spannable) mText;
8270 MetaKeyKeyListener.resetMetaState(sp);
8274 startStopMarquee(focused);
8276 if (mTransformation != null) {
8277 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8280 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8284 public void onWindowFocusChanged(boolean hasWindowFocus) {
8285 super.onWindowFocusChanged(hasWindowFocus);
8287 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8289 startStopMarquee(hasWindowFocus);
8293 protected void onVisibilityChanged(View changedView, int visibility) {
8294 super.onVisibilityChanged(changedView, visibility);
8295 if (mEditor != null && visibility != VISIBLE) {
8296 mEditor.hideCursorAndSpanControllers();
8297 stopTextActionMode();
8302 * Use {@link BaseInputConnection#removeComposingSpans
8303 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8304 * state from this text view.
8306 public void clearComposingText() {
8307 if (mText instanceof Spannable) {
8308 BaseInputConnection.removeComposingSpans((Spannable)mText);
8313 public void setSelected(boolean selected) {
8314 boolean wasSelected = isSelected();
8316 super.setSelected(selected);
8318 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8328 public boolean onTouchEvent(MotionEvent event) {
8329 final int action = event.getActionMasked();
8331 if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
8332 // Detect double tap and inform the Editor.
8333 if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
8334 ViewConfiguration.getDoubleTapTimeout()) {
8335 mEditor.mDoubleTap = true;
8336 mFirstTouch = false;
8338 mEditor.mDoubleTap = false;
8343 if (action == MotionEvent.ACTION_UP) {
8344 mLastTouchUpTime = SystemClock.uptimeMillis();
8347 if (mEditor != null) {
8348 mEditor.onTouchEvent(event);
8350 if (mEditor.mSelectionModifierCursorController != null &&
8351 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8356 final boolean superResult = super.onTouchEvent(event);
8359 * Don't handle the release after a long press, because it will move the selection away from
8360 * whatever the menu action was trying to affect. If the long press should have triggered an
8361 * insertion action mode, we can now actually show it.
8363 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8364 mEditor.mDiscardNextActionUp = false;
8366 if (mEditor.mIsInsertionActionModeStartPending) {
8367 mEditor.startInsertionActionMode();
8368 mEditor.mIsInsertionActionModeStartPending = false;
8373 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8374 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8376 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8377 && mText instanceof Spannable && mLayout != null) {
8378 boolean handled = false;
8380 if (mMovement != null) {
8381 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8384 final boolean textIsSelectable = isTextSelectable();
8385 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8386 // The LinkMovementMethod which should handle taps on links has not been installed
8387 // on non editable text that support text selection.
8388 // We reproduce its behavior here to open links for these.
8389 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8390 getSelectionEnd(), ClickableSpan.class);
8392 if (links.length > 0) {
8393 links[0].onClick(this);
8398 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8399 // Show the IME, except when selecting in read-only text.
8400 final InputMethodManager imm = InputMethodManager.peekInstance();
8402 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8403 handled |= imm != null && imm.showSoftInput(this, 0);
8406 // The above condition ensures that the mEditor is not null
8407 mEditor.onTouchUpEvent(event);
8421 public boolean onGenericMotionEvent(MotionEvent event) {
8422 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8424 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8427 } catch (AbstractMethodError ex) {
8428 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8429 // Ignore its absence in case third party applications implemented the
8430 // interface directly.
8433 return super.onGenericMotionEvent(event);
8437 * @return True iff this TextView contains a text that can be edited, or if this is
8438 * a selectable TextView.
8440 boolean isTextEditable() {
8441 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8445 * Returns true, only while processing a touch gesture, if the initial
8446 * touch down event caused focus to move to the text view and as a result
8447 * its selection changed. Only valid while processing the touch gesture
8448 * of interest, in an editable text view.
8450 public boolean didTouchFocusSelect() {
8451 return mEditor != null && mEditor.mTouchFocusSelected;
8455 public void cancelLongPress() {
8456 super.cancelLongPress();
8457 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8461 public boolean onTrackballEvent(MotionEvent event) {
8462 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8463 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8468 return super.onTrackballEvent(event);
8471 public void setScroller(Scroller s) {
8476 protected float getLeftFadingEdgeStrength() {
8477 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8478 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8479 if (mMarquee != null && !mMarquee.isStopped()) {
8480 final Marquee marquee = mMarquee;
8481 if (marquee.shouldDrawLeftFade()) {
8482 final float scroll = marquee.getScroll();
8483 return scroll / getHorizontalFadingEdgeLength();
8487 } else if (getLineCount() == 1) {
8488 final int layoutDirection = getLayoutDirection();
8489 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8490 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8494 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8495 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8496 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8497 case Gravity.CENTER_HORIZONTAL:
8498 case Gravity.FILL_HORIZONTAL:
8499 final int textDirection = mLayout.getParagraphDirection(0);
8500 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8503 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8504 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8505 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8510 return super.getLeftFadingEdgeStrength();
8514 protected float getRightFadingEdgeStrength() {
8515 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8516 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8517 if (mMarquee != null && !mMarquee.isStopped()) {
8518 final Marquee marquee = mMarquee;
8519 final float maxFadeScroll = marquee.getMaxFadeScroll();
8520 final float scroll = marquee.getScroll();
8521 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8522 } else if (getLineCount() == 1) {
8523 final int layoutDirection = getLayoutDirection();
8524 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8525 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8527 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8528 getCompoundPaddingRight();
8529 final float lineWidth = mLayout.getLineWidth(0);
8530 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8533 case Gravity.CENTER_HORIZONTAL:
8534 case Gravity.FILL_HORIZONTAL:
8535 final int textDirection = mLayout.getParagraphDirection(0);
8536 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8539 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8540 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8541 getHorizontalFadingEdgeLength();
8546 return super.getRightFadingEdgeStrength();
8550 protected int computeHorizontalScrollRange() {
8551 if (mLayout != null) {
8552 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8553 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8556 return super.computeHorizontalScrollRange();
8560 protected int computeVerticalScrollRange() {
8561 if (mLayout != null)
8562 return mLayout.getHeight();
8564 return super.computeVerticalScrollRange();
8568 protected int computeVerticalScrollExtent() {
8569 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8573 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8574 super.findViewsWithText(outViews, searched, flags);
8575 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8576 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8577 String searchedLowerCase = searched.toString().toLowerCase();
8578 String textLowerCase = mText.toString().toLowerCase();
8579 if (textLowerCase.contains(searchedLowerCase)) {
8585 public enum BufferType {
8586 NORMAL, SPANNABLE, EDITABLE,
8590 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8591 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8592 * if TextView_textColor was not set directly.
8596 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8597 if (attrs == null) {
8598 // Preserve behavior prior to removal of this API.
8599 throw new NullPointerException();
8602 // It's not safe to use this method from apps. The parameter 'attrs'
8603 // must have been obtained using the TextView filter array which is not
8604 // available to the SDK. As such, we grab a default TypedArray with the
8605 // right filter instead here.
8606 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8607 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8608 if (colors == null) {
8609 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8611 final TypedArray appearance = context.obtainStyledAttributes(
8612 ap, R.styleable.TextAppearance);
8613 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8614 appearance.recycle();
8623 * Returns the default color from the TextView_textColor attribute from the
8624 * AttributeSet, if set, or the default color from the
8625 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8626 * TextView_textColor was not set directly.
8630 public static int getTextColor(Context context, TypedArray attrs, int def) {
8631 final ColorStateList colors = getTextColors(context, attrs);
8632 if (colors == null) {
8635 return colors.getDefaultColor();
8640 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8641 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8642 // Handle Ctrl-only shortcuts.
8644 case KeyEvent.KEYCODE_A:
8645 if (canSelectText()) {
8646 return onTextContextMenuItem(ID_SELECT_ALL);
8649 case KeyEvent.KEYCODE_Z:
8651 return onTextContextMenuItem(ID_UNDO);
8654 case KeyEvent.KEYCODE_X:
8656 return onTextContextMenuItem(ID_CUT);
8659 case KeyEvent.KEYCODE_C:
8661 return onTextContextMenuItem(ID_COPY);
8664 case KeyEvent.KEYCODE_V:
8666 return onTextContextMenuItem(ID_PASTE);
8670 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8671 // Handle Ctrl-Shift shortcuts.
8673 case KeyEvent.KEYCODE_Z:
8675 return onTextContextMenuItem(ID_REDO);
8678 case KeyEvent.KEYCODE_V:
8680 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8684 return super.onKeyShortcut(keyCode, event);
8688 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8689 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8690 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8693 boolean canSelectText() {
8694 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8698 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8699 * The text must be spannable and the movement method must allow for arbitary selection.
8701 * See also {@link #canSelectText()}.
8703 boolean textCanBeSelected() {
8704 // prepareCursorController() relies on this method.
8705 // If you change this condition, make sure prepareCursorController is called anywhere
8706 // the value of this condition might be changed.
8707 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8708 return isTextEditable() ||
8709 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8712 private Locale getTextServicesLocale(boolean allowNullLocale) {
8713 // Start fetching the text services locale asynchronously.
8714 updateTextServicesLocaleAsync();
8715 // If !allowNullLocale and there is no cached text services locale, just return the default
8717 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8718 : mCurrentSpellCheckerLocaleCache;
8722 * This is a temporary method. Future versions may support multi-locale text.
8723 * Caveat: This method may not return the latest text services locale, but this should be
8724 * acceptable and it's more important to make this method asynchronous.
8726 * @return The locale that should be used for a word iterator
8727 * in this TextView, based on the current spell checker settings,
8728 * the current IME's locale, or the system default locale.
8729 * Please note that a word iterator in this TextView is different from another word iterator
8730 * used by SpellChecker.java of TextView. This method should be used for the former.
8733 // TODO: Support multi-locale
8734 // TODO: Update the text services locale immediately after the keyboard locale is switched
8735 // by catching intent of keyboard switch event
8736 public Locale getTextServicesLocale() {
8737 return getTextServicesLocale(false /* allowNullLocale */);
8741 * @return true if this TextView is specialized for showing and interacting with the extracted
8742 * text in a full-screen input method.
8745 public boolean isInExtractedMode() {
8750 * This is a temporary method. Future versions may support multi-locale text.
8751 * Caveat: This method may not return the latest spell checker locale, but this should be
8752 * acceptable and it's more important to make this method asynchronous.
8754 * @return The locale that should be used for a spell checker in this TextView,
8755 * based on the current spell checker settings, the current IME's locale, or the system default
8759 public Locale getSpellCheckerLocale() {
8760 return getTextServicesLocale(true /* allowNullLocale */);
8763 private void updateTextServicesLocaleAsync() {
8764 // AsyncTask.execute() uses a serial executor which means we don't have
8765 // to lock around updateTextServicesLocaleLocked() to prevent it from
8766 // being executed n times in parallel.
8767 AsyncTask.execute(new Runnable() {
8770 updateTextServicesLocaleLocked();
8775 private void updateTextServicesLocaleLocked() {
8776 final TextServicesManager textServicesManager = (TextServicesManager)
8777 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8778 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8779 final Locale locale;
8780 if (subtype != null) {
8781 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8785 mCurrentSpellCheckerLocaleCache = locale;
8788 void onLocaleChanged() {
8789 // Will be re-created on demand in getWordIterator with the proper new locale
8790 mEditor.mWordIterator = null;
8794 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8795 * Made available to achieve a consistent behavior.
8798 public WordIterator getWordIterator() {
8799 if (mEditor != null) {
8800 return mEditor.getWordIterator();
8808 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8809 super.onPopulateAccessibilityEventInternal(event);
8811 final CharSequence text = getTextForAccessibility();
8812 if (!TextUtils.isEmpty(text)) {
8813 event.getText().add(text);
8818 * @return true if the user has explicitly allowed accessibility services
8819 * to speak passwords.
8821 private boolean shouldSpeakPasswordsForAccessibility() {
8822 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8823 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8824 UserHandle.USER_CURRENT_OR_SELF) == 1);
8828 public CharSequence getAccessibilityClassName() {
8829 return TextView.class.getName();
8833 public void onProvideStructure(ViewStructure structure) {
8834 super.onProvideStructure(structure);
8835 final boolean isPassword = hasPasswordTransformationMethod()
8836 || isPasswordInputType(getInputType());
8838 if (mLayout == null) {
8841 Layout layout = mLayout;
8842 final int lineCount = layout.getLineCount();
8843 if (lineCount <= 1) {
8844 // Simple case: this is a single line.
8845 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8847 // Complex case: multi-line, could be scrolled or within a scroll container
8848 // so some lines are not visible.
8849 final int[] tmpCords = new int[2];
8850 getLocationInWindow(tmpCords);
8851 final int topWindowLocation = tmpCords[1];
8853 ViewParent viewParent = getParent();
8854 while (viewParent instanceof View) {
8855 root = (View) viewParent;
8856 viewParent = root.getParent();
8858 final int windowHeight = root.getHeight();
8860 final int bottomLine;
8861 if (topWindowLocation >= 0) {
8862 // The top of the view is fully within its window; start text at line 0.
8863 topLine = getLineAtCoordinateUnclamped(0);
8864 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
8866 // The top of hte window has scrolled off the top of the window; figure out
8867 // the starting line for this.
8868 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
8869 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
8871 // We want to return some contextual lines above/below the lines that are
8872 // actually visible.
8873 int expandedTopLine = topLine - (bottomLine-topLine)/2;
8874 if (expandedTopLine < 0) {
8875 expandedTopLine = 0;
8877 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
8878 if (expandedBottomLine >= lineCount) {
8879 expandedBottomLine = lineCount-1;
8881 // Convert lines into character offsets.
8882 int expandedTopChar = layout.getLineStart(expandedTopLine);
8883 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
8884 // Take into account selection -- if there is a selection, we need to expand
8885 // the text we are returning to include that selection.
8886 final int selStart = getSelectionStart();
8887 final int selEnd = getSelectionEnd();
8888 if (selStart < selEnd) {
8889 if (selStart < expandedTopChar) {
8890 expandedTopChar = selStart;
8892 if (selEnd > expandedBottomChar) {
8893 expandedBottomChar = selEnd;
8896 // Get the text and trim it to the range we are reporting.
8897 CharSequence text = getText();
8898 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
8899 text = text.subSequence(expandedTopChar, expandedBottomChar);
8901 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
8902 final int[] lineOffsets = new int[bottomLine-topLine+1];
8903 final int[] lineBaselines = new int[bottomLine-topLine+1];
8904 final int baselineOffset = getBaselineOffset();
8905 for (int i=topLine; i<=bottomLine; i++) {
8906 lineOffsets[i-topLine] = layout.getLineStart(i);
8907 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
8909 structure.setTextLines(lineOffsets, lineBaselines);
8912 // Extract style information that applies to the TextView as a whole.
8914 int typefaceStyle = getTypefaceStyle();
8915 if ((typefaceStyle & Typeface.BOLD) != 0) {
8916 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8918 if ((typefaceStyle & Typeface.ITALIC) != 0) {
8919 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
8922 // Global styles can also be set via TextView.setPaintFlags().
8923 int paintFlags = mTextPaint.getFlags();
8924 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
8925 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8927 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
8928 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
8930 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
8931 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
8934 // TextView does not have its own text background color. A background is either part
8935 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
8936 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
8937 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
8939 structure.setHint(getHint());
8944 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
8945 super.onInitializeAccessibilityEventInternal(event);
8947 final boolean isPassword = hasPasswordTransformationMethod();
8948 event.setPassword(isPassword);
8950 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8951 event.setFromIndex(Selection.getSelectionStart(mText));
8952 event.setToIndex(Selection.getSelectionEnd(mText));
8953 event.setItemCount(mText.length());
8959 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
8960 super.onInitializeAccessibilityNodeInfoInternal(info);
8962 final boolean isPassword = hasPasswordTransformationMethod();
8963 info.setPassword(isPassword);
8964 info.setText(getTextForAccessibility());
8966 if (mBufferType == BufferType.EDITABLE) {
8967 info.setEditable(true);
8970 if (mEditor != null) {
8971 info.setInputType(mEditor.mInputType);
8973 if (mEditor.mError != null) {
8974 info.setContentInvalid(true);
8975 info.setError(mEditor.mError);
8979 if (!TextUtils.isEmpty(mText)) {
8980 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8981 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8982 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8983 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8984 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8985 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8986 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8987 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8992 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8995 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8998 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9001 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9002 ACCESSIBILITY_ACTION_SHARE,
9003 getResources().getString(com.android.internal.R.string.share)));
9005 if (canProcessText()) { // also implies mEditor is not null.
9006 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9010 // Check for known input filter types.
9011 final int numFilters = mFilters.length;
9012 for (int i = 0; i < numFilters; i++) {
9013 final InputFilter filter = mFilters[i];
9014 if (filter instanceof InputFilter.LengthFilter) {
9015 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9019 if (!isSingleLine()) {
9020 info.setMultiLine(true);
9025 * Performs an accessibility action after it has been offered to the
9031 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
9033 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9037 case AccessibilityNodeInfo.ACTION_CLICK: {
9038 return performAccessibilityActionClick(arguments);
9040 case AccessibilityNodeInfo.ACTION_COPY: {
9041 if (isFocused() && canCopy()) {
9042 if (onTextContextMenuItem(ID_COPY)) {
9047 case AccessibilityNodeInfo.ACTION_PASTE: {
9048 if (isFocused() && canPaste()) {
9049 if (onTextContextMenuItem(ID_PASTE)) {
9054 case AccessibilityNodeInfo.ACTION_CUT: {
9055 if (isFocused() && canCut()) {
9056 if (onTextContextMenuItem(ID_CUT)) {
9061 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9062 ensureIterableTextForAccessibilitySelectable();
9063 CharSequence text = getIterableTextForAccessibility();
9067 final int start = (arguments != null) ? arguments.getInt(
9068 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9069 final int end = (arguments != null) ? arguments.getInt(
9070 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9071 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9072 // No arguments clears the selection.
9073 if (start == end && end == -1) {
9074 Selection.removeSelection((Spannable) text);
9077 if (start >= 0 && start <= end && end <= text.length()) {
9078 Selection.setSelection((Spannable) text, start, end);
9079 // Make sure selection mode is engaged.
9080 if (mEditor != null) {
9081 mEditor.startSelectionActionMode();
9087 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9088 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9089 ensureIterableTextForAccessibilitySelectable();
9090 return super.performAccessibilityActionInternal(action, arguments);
9092 case ACCESSIBILITY_ACTION_SHARE: {
9093 if (isFocused() && canShare()) {
9094 if (onTextContextMenuItem(ID_SHARE)) {
9100 return super.performAccessibilityActionInternal(action, arguments);
9105 private boolean performAccessibilityActionClick(Bundle arguments) {
9106 boolean handled = false;
9107 boolean processed = false;
9113 if (arguments != null && arguments.containsKey(
9114 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT)) {
9115 int spanIndex = arguments.getInt(
9116 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT, -1);
9117 if (spanIndex >= 0 && hasSpannableText()) {
9118 ClickableSpan[] spans = ((Spannable) mText).getSpans(0,
9119 mText.length(), ClickableSpan.class);
9120 if (spans != null && spans.length > spanIndex && spans[spanIndex] != null) {
9121 // Simulate View.onTouchEvent for an ACTION_UP event
9122 if (isFocusable() && !isFocused()) {
9125 spans[spanIndex].onClick(this);
9132 if (!processed && arguments != null && arguments.containsKey(
9133 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT)) {
9134 int characterIndex = arguments.getInt(
9135 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT, -1);
9136 if (characterIndex >= 0 && hasSpannableText()) {
9137 ClickableSpan[] spans = ((Spannable) mText).getSpans(characterIndex,
9138 characterIndex, ClickableSpan.class);
9139 // click only on the first span to keep parity with onTouch() implementation
9140 if (spans != null && spans.length > 0 && spans[0] != null) {
9141 // Simulate View.onTouchEvent for an ACTION_UP event
9142 if (isFocusable() && !isFocused()) {
9145 spans[0].onClick(this);
9152 if (!processed && (isClickable() || isLongClickable())) {
9153 // Simulate View.onTouchEvent for an ACTION_UP event
9154 if (isFocusable() && !isFocused()) {
9162 // Show the IME, except when selecting in read-only text.
9163 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9164 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9165 final InputMethodManager imm = InputMethodManager.peekInstance();
9167 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9168 handled |= imm.showSoftInput(this, 0);
9175 private boolean hasSpannableText() {
9176 return mText != null && mText instanceof Spannable;
9181 public void sendAccessibilityEventInternal(int eventType) {
9182 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9183 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9186 // Do not send scroll events since first they are not interesting for
9187 // accessibility and second such events a generated too frequently.
9188 // For details see the implementation of bringTextIntoView().
9189 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9192 super.sendAccessibilityEventInternal(eventType);
9196 * Returns the text that should be exposed to accessibility services.
9198 * This approximates what is displayed visually. If the user has specified
9199 * that accessibility services should speak passwords, this method will
9200 * bypass any password transformation method and return unobscured text.
9202 * @return the text that should be exposed to accessibility services, may
9203 * be {@code null} if no text is set
9206 private CharSequence getTextForAccessibility() {
9207 // If the text is empty, we must be showing the hint text.
9208 if (TextUtils.isEmpty(mText)) {
9212 // Check whether we need to bypass the transformation
9213 // method and expose unobscured text.
9214 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9218 // Otherwise, speak whatever text is being displayed.
9219 return mTransformed;
9222 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9223 int fromIndex, int removedCount, int addedCount) {
9224 AccessibilityEvent event =
9225 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9226 event.setFromIndex(fromIndex);
9227 event.setRemovedCount(removedCount);
9228 event.setAddedCount(addedCount);
9229 event.setBeforeText(beforeText);
9230 sendAccessibilityEventUnchecked(event);
9234 * Returns whether this text view is a current input method target. The
9235 * default implementation just checks with {@link InputMethodManager}.
9237 public boolean isInputMethodTarget() {
9238 InputMethodManager imm = InputMethodManager.peekInstance();
9239 return imm != null && imm.isActive(this);
9242 static final int ID_SELECT_ALL = android.R.id.selectAll;
9243 static final int ID_UNDO = android.R.id.undo;
9244 static final int ID_REDO = android.R.id.redo;
9245 static final int ID_CUT = android.R.id.cut;
9246 static final int ID_COPY = android.R.id.copy;
9247 static final int ID_PASTE = android.R.id.paste;
9248 static final int ID_SHARE = android.R.id.shareText;
9249 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9250 static final int ID_REPLACE = android.R.id.replaceText;
9253 * Called when a context menu option for the text view is selected. Currently
9254 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9255 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9257 * @return true if the context menu item action was performed.
9259 public boolean onTextContextMenuItem(int id) {
9261 int max = mText.length();
9264 final int selStart = getSelectionStart();
9265 final int selEnd = getSelectionEnd();
9267 min = Math.max(0, Math.min(selStart, selEnd));
9268 max = Math.max(0, Math.max(selStart, selEnd));
9273 // This starts an action mode if triggered from another action mode. Text is
9274 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9275 // true even if text is empty.
9276 boolean shouldRestartActionMode =
9277 mEditor != null && mEditor.mTextActionMode != null;
9278 stopTextActionMode();
9280 if (shouldRestartActionMode) {
9281 mEditor.startSelectionActionMode();
9286 if (mEditor != null) {
9289 return true; // Returns true even if nothing was undone.
9292 if (mEditor != null) {
9295 return true; // Returns true even if nothing was undone.
9298 paste(min, max, true /* withFormatting */);
9301 case ID_PASTE_AS_PLAIN_TEXT:
9302 paste(min, max, false /* withFormatting */);
9306 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9307 deleteText_internal(min, max);
9308 stopTextActionMode();
9312 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9313 stopTextActionMode();
9317 if (mEditor != null) {
9323 shareSelectedText();
9329 CharSequence getTransformedText(int start, int end) {
9330 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9334 public boolean performLongClick() {
9335 boolean handled = false;
9337 if (super.performLongClick()) {
9341 if (mEditor != null) {
9342 handled |= mEditor.performLongClick(handled);
9346 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9347 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9354 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9355 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9356 if (mEditor != null) {
9357 mEditor.onScrollChanged();
9362 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9363 * by the IME or by the spell checker as the user types. This is done by adding
9364 * {@link SuggestionSpan}s to the text.
9366 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9367 * user asks for them on these parts of the text. This value depends on the inputType of this
9370 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9372 * In addition, the type variation must be one of
9373 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9374 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9375 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9376 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9377 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9379 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9381 * @return true if the suggestions popup window is enabled, based on the inputType.
9383 public boolean isSuggestionsEnabled() {
9384 if (mEditor == null) return false;
9385 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9388 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9390 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9391 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9392 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9393 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9394 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9395 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9399 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9400 * selection is initiated in this View.
9402 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9403 * Paste, Replace and Share actions, depending on what this View supports.
9405 * <p>A custom implementation can add new entries in the default menu in its
9406 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9407 * method. The default actions can also be removed from the menu using
9408 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9409 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9410 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9412 * <p>Returning false from
9413 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9414 * will prevent the action mode from being started.
9416 * <p>Action click events should be handled by the custom implementation of
9417 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9418 * android.view.MenuItem)}.
9420 * <p>Note that text selection mode is not started when a TextView receives focus and the
9421 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9422 * that case, to allow for quick replacement.
9424 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9425 createEditorIfNeeded();
9426 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9430 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9432 * @return The current custom selection callback.
9434 public ActionMode.Callback getCustomSelectionActionModeCallback() {
9435 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9439 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9440 * insertion is initiated in this View.
9441 * The standard implementation populates the menu with a subset of Select All,
9442 * Paste and Replace actions, depending on what this View supports.
9444 * <p>A custom implementation can add new entries in the default menu in its
9445 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9446 * android.view.Menu)} method. The default actions can also be removed from the menu using
9447 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9448 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9450 * <p>Returning false from
9451 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9452 * android.view.Menu)} will prevent the action mode from being started.</p>
9454 * <p>Action click events should be handled by the custom implementation of
9455 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9456 * android.view.MenuItem)}.</p>
9458 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9459 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9461 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9462 createEditorIfNeeded();
9463 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9467 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9469 * @return The current custom insertion callback.
9471 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9472 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9478 protected void stopTextActionMode() {
9479 if (mEditor != null) {
9480 mEditor.stopTextActionMode();
9485 return mEditor != null && mEditor.canUndo();
9489 return mEditor != null && mEditor.canRedo();
9493 if (hasPasswordTransformationMethod()) {
9497 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9498 mEditor.mKeyListener != null) {
9506 if (hasPasswordTransformationMethod()) {
9510 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9517 boolean canShare() {
9521 boolean canPaste() {
9522 return (mText instanceof Editable &&
9523 mEditor != null && mEditor.mKeyListener != null &&
9524 getSelectionStart() >= 0 &&
9525 getSelectionEnd() >= 0 &&
9526 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9530 boolean canProcessText() {
9531 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9532 || hasPasswordTransformationMethod()) {
9536 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9543 boolean canSelectAllText() {
9544 return canSelectText() && !hasPasswordTransformationMethod()
9545 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9548 boolean selectAllText() {
9549 // Need to hide insert point cursor controller before settings selection, otherwise insert
9550 // point cursor controller obtains cursor update event and update cursor with cancelling
9552 if (mEditor != null) {
9553 mEditor.hideInsertionPointCursorController();
9555 final int length = mText.length();
9556 Selection.setSelection((Spannable) mText, 0, length);
9560 void replaceSelectionWithText(CharSequence text) {
9561 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9565 * Paste clipboard content between min and max positions.
9567 private void paste(int min, int max, boolean withFormatting) {
9568 ClipboardManager clipboard =
9569 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9570 ClipData clip = clipboard.getPrimaryClip();
9572 boolean didFirst = false;
9573 for (int i=0; i<clip.getItemCount(); i++) {
9574 final CharSequence paste;
9575 if (withFormatting) {
9576 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9578 // Get an item as text and remove all spans by toString().
9579 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9580 paste = (text instanceof Spanned) ? text.toString() : text;
9582 if (paste != null) {
9584 Selection.setSelection((Spannable) mText, max);
9585 ((Editable) mText).replace(min, max, paste);
9588 ((Editable) mText).insert(getSelectionEnd(), "\n");
9589 ((Editable) mText).insert(getSelectionEnd(), paste);
9593 stopTextActionMode();
9594 sLastCutCopyOrTextChangedTime = 0;
9598 private void shareSelectedText() {
9599 String selectedText = getSelectedText();
9600 if (selectedText != null && !selectedText.isEmpty()) {
9601 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9602 sharingIntent.setType("text/plain");
9603 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9604 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9605 getContext().startActivity(Intent.createChooser(sharingIntent, null));
9606 stopTextActionMode();
9610 private void setPrimaryClip(ClipData clip) {
9611 ClipboardManager clipboard = (ClipboardManager) getContext().
9612 getSystemService(Context.CLIPBOARD_SERVICE);
9613 clipboard.setPrimaryClip(clip);
9614 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9618 * Get the character offset closest to the specified absolute position. A typical use case is to
9619 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9621 * @param x The horizontal absolute position of a point on screen
9622 * @param y The vertical absolute position of a point on screen
9623 * @return the character offset for the character whose position is closest to the specified
9624 * position. Returns -1 if there is no layout.
9626 public int getOffsetForPosition(float x, float y) {
9627 if (getLayout() == null) return -1;
9628 final int line = getLineAtCoordinate(y);
9629 final int offset = getOffsetAtCoordinate(line, x);
9633 float convertToLocalHorizontalCoordinate(float x) {
9634 x -= getTotalPaddingLeft();
9635 // Clamp the position to inside of the view.
9636 x = Math.max(0.0f, x);
9637 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9642 int getLineAtCoordinate(float y) {
9643 y -= getTotalPaddingTop();
9644 // Clamp the position to inside of the view.
9645 y = Math.max(0.0f, y);
9646 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9648 return getLayout().getLineForVertical((int) y);
9651 int getLineAtCoordinateUnclamped(float y) {
9652 y -= getTotalPaddingTop();
9654 return getLayout().getLineForVertical((int) y);
9657 int getOffsetAtCoordinate(int line, float x) {
9658 x = convertToLocalHorizontalCoordinate(x);
9659 return getLayout().getOffsetForHorizontal(line, x);
9663 public boolean onDragEvent(DragEvent event) {
9664 switch (event.getAction()) {
9665 case DragEvent.ACTION_DRAG_STARTED:
9666 return mEditor != null && mEditor.hasInsertionController();
9668 case DragEvent.ACTION_DRAG_ENTERED:
9669 TextView.this.requestFocus();
9672 case DragEvent.ACTION_DRAG_LOCATION:
9673 final int offset = getOffsetForPosition(event.getX(), event.getY());
9674 Selection.setSelection((Spannable)mText, offset);
9677 case DragEvent.ACTION_DROP:
9678 if (mEditor != null) mEditor.onDrop(event);
9681 case DragEvent.ACTION_DRAG_ENDED:
9682 case DragEvent.ACTION_DRAG_EXITED:
9688 boolean isInBatchEditMode() {
9689 if (mEditor == null) return false;
9690 final Editor.InputMethodState ims = mEditor.mInputMethodState;
9692 return ims.mBatchEditNesting > 0;
9694 return mEditor.mInBatchEditControllers;
9698 public void onRtlPropertiesChanged(int layoutDirection) {
9699 super.onRtlPropertiesChanged(layoutDirection);
9701 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9702 if (mTextDir != newTextDir) {
9703 mTextDir = newTextDir;
9704 if (mLayout != null) {
9710 TextDirectionHeuristic getTextDirectionHeuristic() {
9711 if (hasPasswordTransformationMethod()) {
9712 // passwords fields should be LTR
9713 return TextDirectionHeuristics.LTR;
9716 // Always need to resolve layout direction first
9717 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9719 // Now, we can select the heuristic
9720 switch (getTextDirection()) {
9722 case TEXT_DIRECTION_FIRST_STRONG:
9723 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9724 TextDirectionHeuristics.FIRSTSTRONG_LTR);
9725 case TEXT_DIRECTION_ANY_RTL:
9726 return TextDirectionHeuristics.ANYRTL_LTR;
9727 case TEXT_DIRECTION_LTR:
9728 return TextDirectionHeuristics.LTR;
9729 case TEXT_DIRECTION_RTL:
9730 return TextDirectionHeuristics.RTL;
9731 case TEXT_DIRECTION_LOCALE:
9732 return TextDirectionHeuristics.LOCALE;
9733 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9734 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9735 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9736 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9744 public void onResolveDrawables(int layoutDirection) {
9745 // No need to resolve twice
9746 if (mLastLayoutDirection == layoutDirection) {
9749 mLastLayoutDirection = layoutDirection;
9751 // Resolve drawables
9752 if (mDrawables != null) {
9753 mDrawables.resolveWithLayoutDirection(layoutDirection);
9760 protected void resetResolvedDrawables() {
9761 super.resetResolvedDrawables();
9762 mLastLayoutDirection = -1;
9768 protected void viewClicked(InputMethodManager imm) {
9770 imm.viewClicked(this);
9775 * Deletes the range of text [start, end[.
9778 protected void deleteText_internal(int start, int end) {
9779 ((Editable) mText).delete(start, end);
9783 * Replaces the range of text [start, end[ by replacement text
9786 protected void replaceText_internal(int start, int end, CharSequence text) {
9787 ((Editable) mText).replace(start, end, text);
9791 * Sets a span on the specified range of text
9794 protected void setSpan_internal(Object span, int start, int end, int flags) {
9795 ((Editable) mText).setSpan(span, start, end, flags);
9799 * Moves the cursor to the specified offset position in text
9802 protected void setCursorPosition_internal(int start, int end) {
9803 Selection.setSelection(((Editable) mText), start, end);
9807 * An Editor should be created as soon as any of the editable-specific fields (grouped
9808 * inside the Editor object) is assigned to a non-default value.
9809 * This method will create the Editor if needed.
9811 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9812 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9813 * Editor for backward compatibility, as soon as one of these fields is assigned.
9815 * Also note that for performance reasons, the mEditor is created when needed, but not
9816 * reset when no more edit-specific fields are needed.
9818 private void createEditorIfNeeded() {
9819 if (mEditor == null) {
9820 mEditor = new Editor(this);
9828 public CharSequence getIterableTextForAccessibility() {
9832 private void ensureIterableTextForAccessibilitySelectable() {
9833 if (!(mText instanceof Spannable)) {
9834 setText(mText, BufferType.SPANNABLE);
9842 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9843 switch (granularity) {
9844 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9845 Spannable text = (Spannable) getIterableTextForAccessibility();
9846 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9847 AccessibilityIterators.LineTextSegmentIterator iterator =
9848 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9849 iterator.initialize(text, getLayout());
9853 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9854 Spannable text = (Spannable) getIterableTextForAccessibility();
9855 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9856 AccessibilityIterators.PageTextSegmentIterator iterator =
9857 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9858 iterator.initialize(this);
9863 return super.getIteratorForGranularity(granularity);
9870 public int getAccessibilitySelectionStart() {
9871 return getSelectionStart();
9877 public boolean isAccessibilitySelectionExtendable() {
9885 public int getAccessibilitySelectionEnd() {
9886 return getSelectionEnd();
9893 public void setAccessibilitySelection(int start, int end) {
9894 if (getAccessibilitySelectionStart() == start
9895 && getAccessibilitySelectionEnd() == end) {
9898 // Hide all selection controllers used for adjusting selection
9899 // since we are doing so explicitlty by other means and these
9900 // controllers interact with how selection behaves.
9901 if (mEditor != null) {
9902 mEditor.hideCursorAndSpanControllers();
9903 mEditor.stopTextActionMode();
9905 CharSequence text = getIterableTextForAccessibility();
9906 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9907 Selection.setSelection((Spannable) text, start, end);
9909 Selection.removeSelection((Spannable) text);
9915 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
9916 super.encodeProperties(stream);
9918 TruncateAt ellipsize = getEllipsize();
9919 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
9920 stream.addProperty("text:textSize", getTextSize());
9921 stream.addProperty("text:scaledTextSize", getScaledTextSize());
9922 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
9923 stream.addProperty("text:selectionStart", getSelectionStart());
9924 stream.addProperty("text:selectionEnd", getSelectionEnd());
9925 stream.addProperty("text:curTextColor", mCurTextColor);
9926 stream.addProperty("text:text", mText == null ? null : mText.toString());
9927 stream.addProperty("text:gravity", mGravity);
9931 * User interface state that is stored by TextView for implementing
9932 * {@link View#onSaveInstanceState}.
9934 public static class SavedState extends BaseSavedState {
9938 boolean frozenWithFocus;
9940 ParcelableParcel editorState; // Optional state from Editor.
9942 SavedState(Parcelable superState) {
9947 public void writeToParcel(Parcel out, int flags) {
9948 super.writeToParcel(out, flags);
9949 out.writeInt(selStart);
9950 out.writeInt(selEnd);
9951 out.writeInt(frozenWithFocus ? 1 : 0);
9952 TextUtils.writeToParcel(text, out, flags);
9954 if (error == null) {
9958 TextUtils.writeToParcel(error, out, flags);
9961 if (editorState == null) {
9965 editorState.writeToParcel(out, flags);
9970 public String toString() {
9971 String str = "TextView.SavedState{"
9972 + Integer.toHexString(System.identityHashCode(this))
9973 + " start=" + selStart + " end=" + selEnd;
9975 str += " text=" + text;
9980 @SuppressWarnings("hiding")
9981 public static final Parcelable.Creator<SavedState> CREATOR
9982 = new Parcelable.Creator<SavedState>() {
9983 public SavedState createFromParcel(Parcel in) {
9984 return new SavedState(in);
9987 public SavedState[] newArray(int size) {
9988 return new SavedState[size];
9992 private SavedState(Parcel in) {
9994 selStart = in.readInt();
9995 selEnd = in.readInt();
9996 frozenWithFocus = (in.readInt() != 0);
9997 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9999 if (in.readInt() != 0) {
10000 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10003 if (in.readInt() != 0) {
10004 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10009 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10010 private char[] mChars;
10011 private int mStart, mLength;
10013 public CharWrapper(char[] chars, int start, int len) {
10019 /* package */ void set(char[] chars, int start, int len) {
10025 public int length() {
10029 public char charAt(int off) {
10030 return mChars[off + mStart];
10034 public String toString() {
10035 return new String(mChars, mStart, mLength);
10038 public CharSequence subSequence(int start, int end) {
10039 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10040 throw new IndexOutOfBoundsException(start + ", " + end);
10043 return new String(mChars, start + mStart, end - start);
10046 public void getChars(int start, int end, char[] buf, int off) {
10047 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10048 throw new IndexOutOfBoundsException(start + ", " + end);
10051 System.arraycopy(mChars, start + mStart, buf, off, end - start);
10054 public void drawText(Canvas c, int start, int end,
10055 float x, float y, Paint p) {
10056 c.drawText(mChars, start + mStart, end - start, x, y, p);
10059 public void drawTextRun(Canvas c, int start, int end,
10060 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
10061 int count = end - start;
10062 int contextCount = contextEnd - contextStart;
10063 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
10064 contextCount, x, y, isRtl, p);
10067 public float measureText(int start, int end, Paint p) {
10068 return p.measureText(mChars, start + mStart, end - start);
10071 public int getTextWidths(int start, int end, float[] widths, Paint p) {
10072 return p.getTextWidths(mChars, start + mStart, end - start, widths);
10075 public float getTextRunAdvances(int start, int end, int contextStart,
10076 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
10078 int count = end - start;
10079 int contextCount = contextEnd - contextStart;
10080 return p.getTextRunAdvances(mChars, start + mStart, count,
10081 contextStart + mStart, contextCount, isRtl, advances,
10085 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
10086 int offset, int cursorOpt, Paint p) {
10087 int contextCount = contextEnd - contextStart;
10088 return p.getTextRunCursor(mChars, contextStart + mStart,
10089 contextCount, dir, offset + mStart, cursorOpt);
10093 private static final class Marquee {
10094 // TODO: Add an option to configure this
10095 private static final float MARQUEE_DELTA_MAX = 0.07f;
10096 private static final int MARQUEE_DELAY = 1200;
10097 private static final int MARQUEE_DP_PER_SECOND = 30;
10099 private static final byte MARQUEE_STOPPED = 0x0;
10100 private static final byte MARQUEE_STARTING = 0x1;
10101 private static final byte MARQUEE_RUNNING = 0x2;
10103 private final WeakReference<TextView> mView;
10104 private final Choreographer mChoreographer;
10106 private byte mStatus = MARQUEE_STOPPED;
10107 private final float mPixelsPerSecond;
10108 private float mMaxScroll;
10109 private float mMaxFadeScroll;
10110 private float mGhostStart;
10111 private float mGhostOffset;
10112 private float mFadeStop;
10113 private int mRepeatLimit;
10115 private float mScroll;
10116 private long mLastAnimationMs;
10118 Marquee(TextView v) {
10119 final float density = v.getContext().getResources().getDisplayMetrics().density;
10120 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10121 mView = new WeakReference<TextView>(v);
10122 mChoreographer = Choreographer.getInstance();
10125 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10127 public void doFrame(long frameTimeNanos) {
10132 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10134 public void doFrame(long frameTimeNanos) {
10135 mStatus = MARQUEE_RUNNING;
10136 mLastAnimationMs = mChoreographer.getFrameTime();
10141 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10143 public void doFrame(long frameTimeNanos) {
10144 if (mStatus == MARQUEE_RUNNING) {
10145 if (mRepeatLimit >= 0) {
10148 start(mRepeatLimit);
10154 if (mStatus != MARQUEE_RUNNING) {
10158 mChoreographer.removeFrameCallback(mTickCallback);
10160 final TextView textView = mView.get();
10161 if (textView != null && (textView.isFocused() || textView.isSelected())) {
10162 long currentMs = mChoreographer.getFrameTime();
10163 long deltaMs = currentMs - mLastAnimationMs;
10164 mLastAnimationMs = currentMs;
10165 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10166 mScroll += deltaPx;
10167 if (mScroll > mMaxScroll) {
10168 mScroll = mMaxScroll;
10169 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10171 mChoreographer.postFrameCallback(mTickCallback);
10173 textView.invalidate();
10178 mStatus = MARQUEE_STOPPED;
10179 mChoreographer.removeFrameCallback(mStartCallback);
10180 mChoreographer.removeFrameCallback(mRestartCallback);
10181 mChoreographer.removeFrameCallback(mTickCallback);
10185 private void resetScroll() {
10187 final TextView textView = mView.get();
10188 if (textView != null) textView.invalidate();
10191 void start(int repeatLimit) {
10192 if (repeatLimit == 0) {
10196 mRepeatLimit = repeatLimit;
10197 final TextView textView = mView.get();
10198 if (textView != null && textView.mLayout != null) {
10199 mStatus = MARQUEE_STARTING;
10201 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10202 textView.getCompoundPaddingRight();
10203 final float lineWidth = textView.mLayout.getLineWidth(0);
10204 final float gap = textWidth / 3.0f;
10205 mGhostStart = lineWidth - textWidth + gap;
10206 mMaxScroll = mGhostStart + textWidth;
10207 mGhostOffset = lineWidth + gap;
10208 mFadeStop = lineWidth + textWidth / 6.0f;
10209 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10211 textView.invalidate();
10212 mChoreographer.postFrameCallback(mStartCallback);
10216 float getGhostOffset() {
10217 return mGhostOffset;
10220 float getScroll() {
10224 float getMaxFadeScroll() {
10225 return mMaxFadeScroll;
10228 boolean shouldDrawLeftFade() {
10229 return mScroll <= mFadeStop;
10232 boolean shouldDrawGhost() {
10233 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10236 boolean isRunning() {
10237 return mStatus == MARQUEE_RUNNING;
10240 boolean isStopped() {
10241 return mStatus == MARQUEE_STOPPED;
10245 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10247 private CharSequence mBeforeText;
10249 public void beforeTextChanged(CharSequence buffer, int start,
10250 int before, int after) {
10251 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10252 + " before=" + before + " after=" + after + ": " + buffer);
10254 if (AccessibilityManager.getInstance(mContext).isEnabled()
10255 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10256 || shouldSpeakPasswordsForAccessibility())) {
10257 mBeforeText = buffer.toString();
10260 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10263 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10264 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10265 + " before=" + before + " after=" + after + ": " + buffer);
10266 TextView.this.handleTextChanged(buffer, start, before, after);
10268 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10269 (isFocused() || isSelected() && isShown())) {
10270 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10271 mBeforeText = null;
10275 public void afterTextChanged(Editable buffer) {
10276 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10277 TextView.this.sendAfterTextChanged(buffer);
10279 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10280 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10284 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10285 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10286 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10287 TextView.this.spanChange(buf, what, s, st, e, en);
10290 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10291 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10292 + " what=" + what + ": " + buf);
10293 TextView.this.spanChange(buf, what, -1, s, -1, e);
10296 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10297 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10298 + " what=" + what + ": " + buf);
10299 TextView.this.spanChange(buf, what, s, -1, e, -1);