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.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
22 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
25 import android.annotation.ColorInt;
26 import android.annotation.DrawableRes;
27 import android.annotation.FloatRange;
28 import android.annotation.IntDef;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.annotation.Size;
32 import android.annotation.StringRes;
33 import android.annotation.StyleRes;
34 import android.annotation.XmlRes;
35 import android.app.Activity;
36 import android.app.assist.AssistStructure;
37 import android.content.ClipData;
38 import android.content.ClipDescription;
39 import android.content.ClipboardManager;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.UndoManager;
43 import android.content.res.ColorStateList;
44 import android.content.res.CompatibilityInfo;
45 import android.content.res.Configuration;
46 import android.content.res.Resources;
47 import android.content.res.TypedArray;
48 import android.content.res.XmlResourceParser;
49 import android.graphics.BaseCanvas;
50 import android.graphics.Canvas;
51 import android.graphics.Insets;
52 import android.graphics.Paint;
53 import android.graphics.Path;
54 import android.graphics.PorterDuff;
55 import android.graphics.Rect;
56 import android.graphics.RectF;
57 import android.graphics.Typeface;
58 import android.graphics.drawable.Drawable;
59 import android.graphics.fonts.FontVariationAxis;
60 import android.icu.text.DecimalFormatSymbols;
61 import android.os.AsyncTask;
62 import android.os.Build.VERSION_CODES;
63 import android.os.Bundle;
64 import android.os.LocaleList;
65 import android.os.Parcel;
66 import android.os.Parcelable;
67 import android.os.ParcelableParcel;
68 import android.os.SystemClock;
69 import android.provider.Settings;
70 import android.text.BoringLayout;
71 import android.text.DynamicLayout;
72 import android.text.Editable;
73 import android.text.GetChars;
74 import android.text.GraphicsOperations;
75 import android.text.InputFilter;
76 import android.text.InputType;
77 import android.text.Layout;
78 import android.text.ParcelableSpan;
79 import android.text.Selection;
80 import android.text.SpanWatcher;
81 import android.text.Spannable;
82 import android.text.SpannableStringBuilder;
83 import android.text.Spanned;
84 import android.text.SpannedString;
85 import android.text.StaticLayout;
86 import android.text.TextDirectionHeuristic;
87 import android.text.TextDirectionHeuristics;
88 import android.text.TextPaint;
89 import android.text.TextUtils;
90 import android.text.TextUtils.TruncateAt;
91 import android.text.TextWatcher;
92 import android.text.method.AllCapsTransformationMethod;
93 import android.text.method.ArrowKeyMovementMethod;
94 import android.text.method.DateKeyListener;
95 import android.text.method.DateTimeKeyListener;
96 import android.text.method.DialerKeyListener;
97 import android.text.method.DigitsKeyListener;
98 import android.text.method.KeyListener;
99 import android.text.method.LinkMovementMethod;
100 import android.text.method.MetaKeyKeyListener;
101 import android.text.method.MovementMethod;
102 import android.text.method.PasswordTransformationMethod;
103 import android.text.method.SingleLineTransformationMethod;
104 import android.text.method.TextKeyListener;
105 import android.text.method.TimeKeyListener;
106 import android.text.method.TransformationMethod;
107 import android.text.method.TransformationMethod2;
108 import android.text.method.WordIterator;
109 import android.text.style.CharacterStyle;
110 import android.text.style.ClickableSpan;
111 import android.text.style.ParagraphStyle;
112 import android.text.style.SpellCheckSpan;
113 import android.text.style.SuggestionSpan;
114 import android.text.style.URLSpan;
115 import android.text.style.UpdateAppearance;
116 import android.text.util.Linkify;
117 import android.util.AttributeSet;
118 import android.util.DisplayMetrics;
119 import android.util.IntArray;
120 import android.util.Log;
121 import android.util.TypedValue;
122 import android.view.AccessibilityIterators.TextSegmentIterator;
123 import android.view.ActionMode;
124 import android.view.Choreographer;
125 import android.view.ContextMenu;
126 import android.view.DragEvent;
127 import android.view.Gravity;
128 import android.view.HapticFeedbackConstants;
129 import android.view.InputDevice;
130 import android.view.KeyCharacterMap;
131 import android.view.KeyEvent;
132 import android.view.MotionEvent;
133 import android.view.PointerIcon;
134 import android.view.View;
135 import android.view.ViewConfiguration;
136 import android.view.ViewDebug;
137 import android.view.ViewGroup.LayoutParams;
138 import android.view.ViewHierarchyEncoder;
139 import android.view.ViewParent;
140 import android.view.ViewRootImpl;
141 import android.view.ViewStructure;
142 import android.view.ViewTreeObserver;
143 import android.view.accessibility.AccessibilityEvent;
144 import android.view.accessibility.AccessibilityManager;
145 import android.view.accessibility.AccessibilityNodeInfo;
146 import android.view.animation.AnimationUtils;
147 import android.view.autofill.AutofillManager;
148 import android.view.autofill.AutofillValue;
149 import android.view.inputmethod.BaseInputConnection;
150 import android.view.inputmethod.CompletionInfo;
151 import android.view.inputmethod.CorrectionInfo;
152 import android.view.inputmethod.CursorAnchorInfo;
153 import android.view.inputmethod.EditorInfo;
154 import android.view.inputmethod.ExtractedText;
155 import android.view.inputmethod.ExtractedTextRequest;
156 import android.view.inputmethod.InputConnection;
157 import android.view.inputmethod.InputMethodManager;
158 import android.view.textclassifier.TextClassificationManager;
159 import android.view.textclassifier.TextClassifier;
160 import android.view.textservice.SpellCheckerSubtype;
161 import android.view.textservice.TextServicesManager;
162 import android.widget.RemoteViews.RemoteView;
164 import com.android.internal.annotations.VisibleForTesting;
165 import com.android.internal.logging.MetricsLogger;
166 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
167 import com.android.internal.util.FastMath;
168 import com.android.internal.widget.EditableInputConnection;
170 import libcore.util.EmptyArray;
172 import org.xmlpull.v1.XmlPullParserException;
174 import java.io.IOException;
175 import java.lang.annotation.Retention;
176 import java.lang.annotation.RetentionPolicy;
177 import java.lang.ref.WeakReference;
178 import java.util.ArrayList;
179 import java.util.Arrays;
180 import java.util.Locale;
183 * A user interface element that displays text to the user.
184 * To provide user-editable text, see {@link EditText}.
186 * The following code sample shows a typical use, with an XML layout
187 * and code to modify the contents of the text view:
192 xmlns:android="http://schemas.android.com/apk/res/android"
193 android:layout_width="match_parent"
194 android:layout_height="match_parent">
196 * android:id="@+id/text_view_id"
197 * android:layout_height="wrap_content"
198 * android:layout_width="wrap_content"
199 * android:text="@string/hello" />
200 * </LinearLayout>
203 * This code sample demonstrates how to modify the contents of the text view
204 * defined in the previous XML layout:
207 * public class MainActivity extends Activity {
209 * protected void onCreate(Bundle savedInstanceState) {
210 * super.onCreate(savedInstanceState);
211 * setContentView(R.layout.activity_main);
212 * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
213 * helloTextView.setText(R.string.user_greeting);
218 * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>.
221 * <b>XML attributes</b>
223 * See {@link android.R.styleable#TextView TextView Attributes},
224 * {@link android.R.styleable#View View Attributes}
226 * @attr ref android.R.styleable#TextView_text
227 * @attr ref android.R.styleable#TextView_bufferType
228 * @attr ref android.R.styleable#TextView_hint
229 * @attr ref android.R.styleable#TextView_textColor
230 * @attr ref android.R.styleable#TextView_textColorHighlight
231 * @attr ref android.R.styleable#TextView_textColorHint
232 * @attr ref android.R.styleable#TextView_textAppearance
233 * @attr ref android.R.styleable#TextView_textColorLink
234 * @attr ref android.R.styleable#TextView_textSize
235 * @attr ref android.R.styleable#TextView_textScaleX
236 * @attr ref android.R.styleable#TextView_fontFamily
237 * @attr ref android.R.styleable#TextView_typeface
238 * @attr ref android.R.styleable#TextView_textStyle
239 * @attr ref android.R.styleable#TextView_cursorVisible
240 * @attr ref android.R.styleable#TextView_maxLines
241 * @attr ref android.R.styleable#TextView_maxHeight
242 * @attr ref android.R.styleable#TextView_lines
243 * @attr ref android.R.styleable#TextView_height
244 * @attr ref android.R.styleable#TextView_minLines
245 * @attr ref android.R.styleable#TextView_minHeight
246 * @attr ref android.R.styleable#TextView_maxEms
247 * @attr ref android.R.styleable#TextView_maxWidth
248 * @attr ref android.R.styleable#TextView_ems
249 * @attr ref android.R.styleable#TextView_width
250 * @attr ref android.R.styleable#TextView_minEms
251 * @attr ref android.R.styleable#TextView_minWidth
252 * @attr ref android.R.styleable#TextView_gravity
253 * @attr ref android.R.styleable#TextView_scrollHorizontally
254 * @attr ref android.R.styleable#TextView_password
255 * @attr ref android.R.styleable#TextView_singleLine
256 * @attr ref android.R.styleable#TextView_selectAllOnFocus
257 * @attr ref android.R.styleable#TextView_includeFontPadding
258 * @attr ref android.R.styleable#TextView_maxLength
259 * @attr ref android.R.styleable#TextView_shadowColor
260 * @attr ref android.R.styleable#TextView_shadowDx
261 * @attr ref android.R.styleable#TextView_shadowDy
262 * @attr ref android.R.styleable#TextView_shadowRadius
263 * @attr ref android.R.styleable#TextView_autoLink
264 * @attr ref android.R.styleable#TextView_linksClickable
265 * @attr ref android.R.styleable#TextView_numeric
266 * @attr ref android.R.styleable#TextView_digits
267 * @attr ref android.R.styleable#TextView_phoneNumber
268 * @attr ref android.R.styleable#TextView_inputMethod
269 * @attr ref android.R.styleable#TextView_capitalize
270 * @attr ref android.R.styleable#TextView_autoText
271 * @attr ref android.R.styleable#TextView_editable
272 * @attr ref android.R.styleable#TextView_freezesText
273 * @attr ref android.R.styleable#TextView_ellipsize
274 * @attr ref android.R.styleable#TextView_drawableTop
275 * @attr ref android.R.styleable#TextView_drawableBottom
276 * @attr ref android.R.styleable#TextView_drawableRight
277 * @attr ref android.R.styleable#TextView_drawableLeft
278 * @attr ref android.R.styleable#TextView_drawableStart
279 * @attr ref android.R.styleable#TextView_drawableEnd
280 * @attr ref android.R.styleable#TextView_drawablePadding
281 * @attr ref android.R.styleable#TextView_drawableTint
282 * @attr ref android.R.styleable#TextView_drawableTintMode
283 * @attr ref android.R.styleable#TextView_lineSpacingExtra
284 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
285 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
286 * @attr ref android.R.styleable#TextView_inputType
287 * @attr ref android.R.styleable#TextView_imeOptions
288 * @attr ref android.R.styleable#TextView_privateImeOptions
289 * @attr ref android.R.styleable#TextView_imeActionLabel
290 * @attr ref android.R.styleable#TextView_imeActionId
291 * @attr ref android.R.styleable#TextView_editorExtras
292 * @attr ref android.R.styleable#TextView_elegantTextHeight
293 * @attr ref android.R.styleable#TextView_letterSpacing
294 * @attr ref android.R.styleable#TextView_fontFeatureSettings
295 * @attr ref android.R.styleable#TextView_breakStrategy
296 * @attr ref android.R.styleable#TextView_hyphenationFrequency
297 * @attr ref android.R.styleable#TextView_autoSizeTextType
298 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
299 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
300 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
301 * @attr ref android.R.styleable#TextView_autoSizePresetSizes
304 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
305 static final String LOG_TAG = "TextView";
306 static final boolean DEBUG_EXTRACT = false;
307 static final boolean DEBUG_AUTOFILL = false;
308 private static final float[] TEMP_POSITION = new float[2];
310 // Enum for the "typeface" XML parameter.
311 // TODO: How can we get this from the XML instead of hardcoding it here?
312 private static final int SANS = 1;
313 private static final int SERIF = 2;
314 private static final int MONOSPACE = 3;
316 // Bitfield for the "numeric" XML parameter.
317 // TODO: How can we get this from the XML instead of hardcoding it here?
318 private static final int SIGNED = 2;
319 private static final int DECIMAL = 4;
322 * Draw marquee text with fading edges as usual
324 private static final int MARQUEE_FADE_NORMAL = 0;
327 * Draw marquee text as ellipsize end while inactive instead of with the fade.
328 * (Useful for devices where the fade can be expensive if overdone)
330 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
333 * Draw marquee text with fading edges because it is currently active/animating.
335 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
337 private static final int LINES = 1;
338 private static final int EMS = LINES;
339 private static final int PIXELS = 2;
341 private static final RectF TEMP_RECTF = new RectF();
344 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
345 private static final int ANIMATED_SCROLL_GAP = 250;
347 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
348 private static final Spanned EMPTY_SPANNED = new SpannedString("");
350 private static final int CHANGE_WATCHER_PRIORITY = 100;
352 // New state used to change background based on whether this TextView is multiline.
353 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
355 // Accessibility action to share selected text.
356 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
361 // Accessibility action start id for "process text" actions.
362 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
367 static final int PROCESS_TEXT_REQUEST_CODE = 100;
370 * Return code of {@link #doKeyDown}.
372 private static final int KEY_EVENT_NOT_HANDLED = 0;
373 private static final int KEY_EVENT_HANDLED = -1;
374 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
375 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
377 private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
379 // System wide time for last cut, copy or text changed action.
380 static long sLastCutCopyOrTextChangedTime;
382 private ColorStateList mTextColor;
383 private ColorStateList mHintTextColor;
384 private ColorStateList mLinkTextColor;
385 @ViewDebug.ExportedProperty(category = "text")
386 private int mCurTextColor;
387 private int mCurHintTextColor;
388 private boolean mFreezesText;
390 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
391 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
393 private float mShadowRadius, mShadowDx, mShadowDy;
394 private int mShadowColor;
396 private boolean mPreDrawRegistered;
397 private boolean mPreDrawListenerDetached;
399 private TextClassifier mTextClassifier;
401 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
402 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
403 // the view hierarchy. On the other hand, if the user is using the movement key to traverse
404 // views (i.e. the first movement was to traverse out of this view, or this view was traversed
405 // into by the user holding the movement key down) then we shouldn't prevent the focus from
407 private boolean mPreventDefaultMovement;
409 private TextUtils.TruncateAt mEllipsize;
411 static class Drawables {
412 static final int LEFT = 0;
413 static final int TOP = 1;
414 static final int RIGHT = 2;
415 static final int BOTTOM = 3;
417 static final int DRAWABLE_NONE = -1;
418 static final int DRAWABLE_RIGHT = 0;
419 static final int DRAWABLE_LEFT = 1;
421 final Rect mCompoundRect = new Rect();
423 final Drawable[] mShowing = new Drawable[4];
425 ColorStateList mTintList;
426 PorterDuff.Mode mTintMode;
428 boolean mHasTintMode;
430 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
431 Drawable mDrawableLeftInitial, mDrawableRightInitial;
433 boolean mIsRtlCompatibilityMode;
436 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
437 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
439 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
440 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
442 int mDrawablePadding;
444 int mDrawableSaved = DRAWABLE_NONE;
446 public Drawables(Context context) {
447 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
448 mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1
449 || !context.getApplicationInfo().hasRtlSupport();
454 * @return {@code true} if this object contains metadata that needs to
455 * be retained, {@code false} otherwise
457 public boolean hasMetadata() {
458 return mDrawablePadding != 0 || mHasTintMode || mHasTint;
462 * Updates the list of displayed drawables to account for the current
465 * @param layoutDirection the current layout direction
466 * @return {@code true} if the displayed drawables changed
468 public boolean resolveWithLayoutDirection(int layoutDirection) {
469 final Drawable previousLeft = mShowing[Drawables.LEFT];
470 final Drawable previousRight = mShowing[Drawables.RIGHT];
472 // First reset "left" and "right" drawables to their initial values
473 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
474 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
476 if (mIsRtlCompatibilityMode) {
477 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
478 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
479 mShowing[Drawables.LEFT] = mDrawableStart;
480 mDrawableSizeLeft = mDrawableSizeStart;
481 mDrawableHeightLeft = mDrawableHeightStart;
483 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
484 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
485 mShowing[Drawables.RIGHT] = mDrawableEnd;
486 mDrawableSizeRight = mDrawableSizeEnd;
487 mDrawableHeightRight = mDrawableHeightEnd;
490 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
491 // drawable if and only if they have been defined
492 switch(layoutDirection) {
493 case LAYOUT_DIRECTION_RTL:
495 mShowing[Drawables.RIGHT] = mDrawableStart;
496 mDrawableSizeRight = mDrawableSizeStart;
497 mDrawableHeightRight = mDrawableHeightStart;
499 mShowing[Drawables.LEFT] = mDrawableEnd;
500 mDrawableSizeLeft = mDrawableSizeEnd;
501 mDrawableHeightLeft = mDrawableHeightEnd;
505 case LAYOUT_DIRECTION_LTR:
508 mShowing[Drawables.LEFT] = mDrawableStart;
509 mDrawableSizeLeft = mDrawableSizeStart;
510 mDrawableHeightLeft = mDrawableHeightStart;
512 mShowing[Drawables.RIGHT] = mDrawableEnd;
513 mDrawableSizeRight = mDrawableSizeEnd;
514 mDrawableHeightRight = mDrawableHeightEnd;
520 applyErrorDrawableIfNeeded(layoutDirection);
522 return mShowing[Drawables.LEFT] != previousLeft
523 || mShowing[Drawables.RIGHT] != previousRight;
526 public void setErrorDrawable(Drawable dr, TextView tv) {
527 if (mDrawableError != dr && mDrawableError != null) {
528 mDrawableError.setCallback(null);
532 if (mDrawableError != null) {
533 final Rect compoundRect = mCompoundRect;
534 final int[] state = tv.getDrawableState();
536 mDrawableError.setState(state);
537 mDrawableError.copyBounds(compoundRect);
538 mDrawableError.setCallback(tv);
539 mDrawableSizeError = compoundRect.width();
540 mDrawableHeightError = compoundRect.height();
542 mDrawableSizeError = mDrawableHeightError = 0;
546 private void applyErrorDrawableIfNeeded(int layoutDirection) {
547 // first restore the initial state if needed
548 switch (mDrawableSaved) {
550 mShowing[Drawables.LEFT] = mDrawableTemp;
551 mDrawableSizeLeft = mDrawableSizeTemp;
552 mDrawableHeightLeft = mDrawableHeightTemp;
555 mShowing[Drawables.RIGHT] = mDrawableTemp;
556 mDrawableSizeRight = mDrawableSizeTemp;
557 mDrawableHeightRight = mDrawableHeightTemp;
562 // then, if needed, assign the Error drawable to the correct location
563 if (mDrawableError != null) {
564 switch(layoutDirection) {
565 case LAYOUT_DIRECTION_RTL:
566 mDrawableSaved = DRAWABLE_LEFT;
568 mDrawableTemp = mShowing[Drawables.LEFT];
569 mDrawableSizeTemp = mDrawableSizeLeft;
570 mDrawableHeightTemp = mDrawableHeightLeft;
572 mShowing[Drawables.LEFT] = mDrawableError;
573 mDrawableSizeLeft = mDrawableSizeError;
574 mDrawableHeightLeft = mDrawableHeightError;
576 case LAYOUT_DIRECTION_LTR:
578 mDrawableSaved = DRAWABLE_RIGHT;
580 mDrawableTemp = mShowing[Drawables.RIGHT];
581 mDrawableSizeTemp = mDrawableSizeRight;
582 mDrawableHeightTemp = mDrawableHeightRight;
584 mShowing[Drawables.RIGHT] = mDrawableError;
585 mDrawableSizeRight = mDrawableSizeError;
586 mDrawableHeightRight = mDrawableHeightError;
593 Drawables mDrawables;
595 private CharWrapper mCharWrapper;
597 private Marquee mMarquee;
598 private boolean mRestartMarquee;
600 private int mMarqueeRepeatLimit = 3;
602 private int mLastLayoutDirection = -1;
605 * On some devices the fading edges add a performance penalty if used
606 * extensively in the same layout. This mode indicates how the marquee
607 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
609 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
612 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
613 * the layout that should be used when the mode switches.
615 private Layout mSavedMarqueeModeLayout;
617 @ViewDebug.ExportedProperty(category = "text")
618 private CharSequence mText;
619 private CharSequence mTransformed;
620 private BufferType mBufferType = BufferType.NORMAL;
622 private CharSequence mHint;
623 private Layout mHintLayout;
625 private MovementMethod mMovement;
627 private TransformationMethod mTransformation;
628 private boolean mAllowTransformationLengthChange;
629 private ChangeWatcher mChangeWatcher;
631 private ArrayList<TextWatcher> mListeners;
633 // display attributes
634 private final TextPaint mTextPaint;
635 private boolean mUserSetTextScaleX;
636 private Layout mLayout;
637 private boolean mLocalesChanged = false;
639 // True if setKeyListener() has been explicitly called
640 private boolean mListenerChanged = false;
641 // True if internationalized input should be used for numbers and date and time.
642 private final boolean mUseInternationalizedInput;
644 @ViewDebug.ExportedProperty(category = "text")
645 private int mGravity = Gravity.TOP | Gravity.START;
646 private boolean mHorizontallyScrolling;
648 private int mAutoLinkMask;
649 private boolean mLinksClickable = true;
651 private float mSpacingMult = 1.0f;
652 private float mSpacingAdd = 0.0f;
654 private int mBreakStrategy;
655 private int mHyphenationFrequency;
656 private int mJustificationMode;
658 private int mMaximum = Integer.MAX_VALUE;
659 private int mMaxMode = LINES;
660 private int mMinimum = 0;
661 private int mMinMode = LINES;
663 private int mOldMaximum = mMaximum;
664 private int mOldMaxMode = mMaxMode;
666 private int mMaxWidth = Integer.MAX_VALUE;
667 private int mMaxWidthMode = PIXELS;
668 private int mMinWidth = 0;
669 private int mMinWidthMode = PIXELS;
671 private boolean mSingleLine;
672 private int mDesiredHeightAtMeasure = -1;
673 private boolean mIncludePad = true;
674 private int mDeferScroll = -1;
676 // tmp primitives, so we don't alloc them on each draw
677 private Rect mTempRect;
678 private long mLastScroll;
679 private Scroller mScroller;
680 private TextPaint mTempTextPaint;
682 private BoringLayout.Metrics mBoring, mHintBoring;
683 private BoringLayout mSavedLayout, mSavedHintLayout;
685 private TextDirectionHeuristic mTextDir;
687 private InputFilter[] mFilters = NO_FILTERS;
689 private volatile Locale mCurrentSpellCheckerLocaleCache;
691 // It is possible to have a selection even when mEditor is null (programmatically set, like when
692 // a link is pressed). These highlight-related fields do not go in mEditor.
693 int mHighlightColor = 0x6633B5E5;
694 private Path mHighlightPath;
695 private final Paint mHighlightPaint;
696 private boolean mHighlightPathBogus = true;
698 // Although these fields are specific to editable text, they are not added to Editor because
699 // they are defined by the TextView's style and are theme-dependent.
700 int mCursorDrawableRes;
701 // These six fields, could be moved to Editor, since we know their default values and we
702 // could condition the creation of the Editor to a non standard value. This is however
703 // brittle since the hardcoded values here (such as
704 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
705 // default style is modified.
706 int mTextSelectHandleLeftRes;
707 int mTextSelectHandleRightRes;
708 int mTextSelectHandleRes;
709 int mTextEditSuggestionItemLayout;
710 int mTextEditSuggestionContainerLayout;
711 int mTextEditSuggestionHighlightStyle;
714 * {@link EditText} specific data, created on demand when one of the Editor fields is used.
715 * See {@link #createEditorIfNeeded()}.
717 private Editor mEditor;
719 private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
720 private static final int DEVICE_PROVISIONED_NO = 1;
721 private static final int DEVICE_PROVISIONED_YES = 2;
724 * Some special options such as sharing selected text should only be shown if the device
725 * is provisioned. Only check the provisioned state once for a given view instance.
727 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
730 * The TextView does not auto-size text (default).
732 public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
735 * The TextView scales text size both horizontally and vertically to fit within the
738 public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
741 @IntDef({AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM})
742 @Retention(RetentionPolicy.SOURCE)
743 public @interface AutoSizeTextType {}
744 // Default minimum size for auto-sizing text in scaled pixels.
745 private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12;
746 // Default maximum size for auto-sizing text in scaled pixels.
747 private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112;
748 // Default value for the step size in pixels.
749 private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
750 // Use this to specify that any of the auto-size configuration int values have not been set.
751 private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
752 // Auto-size text type.
753 private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
754 // Specify if auto-size text is needed.
755 private boolean mNeedsAutoSizeText = false;
756 // Step size for auto-sizing in pixels.
757 private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
758 // Minimum text size for auto-sizing in pixels.
759 private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
760 // Maximum text size for auto-sizing in pixels.
761 private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
762 // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from
763 // when auto-sizing text.
764 private int[] mAutoSizeTextSizesInPx = EmptyArray.INT;
765 // Specifies whether auto-size should use the provided auto size steps set or if it should
766 // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and
767 // mAutoSizeStepGranularityInPx.
768 private boolean mHasPresetAutoSizeValues = false;
770 // Indicates whether the text was set from resources or dynamically, so it can be used to
771 // sanitize autofill requests.
772 private boolean mTextFromResource = false;
775 * Kick-start the font cache for the zygote process (to pay the cost of
776 * initializing freetype for our default font only once).
779 public static void preloadFontCache() {
780 Paint p = new Paint();
781 p.setAntiAlias(true);
782 // Ensure that the Typeface is loaded here.
783 // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto.
784 // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here
785 // since Paint.measureText can not be called without Typeface static initializer.
786 p.setTypeface(Typeface.DEFAULT);
787 // We don't care about the result, just the side-effect of measuring.
792 * Interface definition for a callback to be invoked when an action is
793 * performed on the editor.
795 public interface OnEditorActionListener {
797 * Called when an action is being performed.
799 * @param v The view that was clicked.
800 * @param actionId Identifier of the action. This will be either the
801 * identifier you supplied, or {@link EditorInfo#IME_NULL
802 * EditorInfo.IME_NULL} if being called due to the enter key
804 * @param event If triggered by an enter key, this is the event;
805 * otherwise, this is null.
806 * @return Return true if you have consumed the action, else false.
808 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
811 public TextView(Context context) {
815 public TextView(Context context, @Nullable AttributeSet attrs) {
816 this(context, attrs, com.android.internal.R.attr.textViewStyle);
819 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
820 this(context, attrs, defStyleAttr, 0);
823 @SuppressWarnings("deprecation")
825 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
826 super(context, attrs, defStyleAttr, defStyleRes);
828 // TextView is important by default, unless app developer overrode attribute.
829 if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
830 setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
835 final Resources res = getResources();
836 final CompatibilityInfo compat = res.getCompatibilityInfo();
838 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
839 mTextPaint.density = res.getDisplayMetrics().density;
840 mTextPaint.setCompatibilityScaling(compat.applicationScale);
842 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
843 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
845 mMovement = getDefaultMovementMethod();
847 mTransformation = null;
849 int textColorHighlight = 0;
850 ColorStateList textColor = null;
851 ColorStateList textColorHint = null;
852 ColorStateList textColorLink = null;
854 String fontFamily = null;
855 Typeface fontTypeface = null;
856 boolean fontFamilyExplicit = false;
857 int typefaceIndex = -1;
859 boolean allCaps = false;
861 float dx = 0, dy = 0, r = 0;
862 boolean elegant = false;
863 float letterSpacing = 0;
864 String fontFeatureSettings = null;
865 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
866 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
867 mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
869 final Resources.Theme theme = context.getTheme();
872 * Look the appearance up without checking first if it exists because
873 * almost every TextView has one and it greatly simplifies the logic
874 * to be able to parse the appearance first and then let specific tags
875 * for this View override it.
877 TypedArray a = theme.obtainStyledAttributes(attrs,
878 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
879 TypedArray appearance = null;
880 int ap = a.getResourceId(
881 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
884 appearance = theme.obtainStyledAttributes(
885 ap, com.android.internal.R.styleable.TextAppearance);
887 if (appearance != null) {
888 int n = appearance.getIndexCount();
889 for (int i = 0; i < n; i++) {
890 int attr = appearance.getIndex(i);
893 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
894 textColorHighlight = appearance.getColor(attr, textColorHighlight);
897 case com.android.internal.R.styleable.TextAppearance_textColor:
898 textColor = appearance.getColorStateList(attr);
901 case com.android.internal.R.styleable.TextAppearance_textColorHint:
902 textColorHint = appearance.getColorStateList(attr);
905 case com.android.internal.R.styleable.TextAppearance_textColorLink:
906 textColorLink = appearance.getColorStateList(attr);
909 case com.android.internal.R.styleable.TextAppearance_textSize:
910 textSize = appearance.getDimensionPixelSize(attr, textSize);
913 case com.android.internal.R.styleable.TextAppearance_typeface:
914 typefaceIndex = appearance.getInt(attr, -1);
917 case com.android.internal.R.styleable.TextAppearance_fontFamily:
918 if (!context.isRestricted() && context.canLoadUnsafeResources()) {
920 fontTypeface = appearance.getFont(attr);
921 } catch (UnsupportedOperationException
922 | Resources.NotFoundException e) {
923 // Expected if it is not a font resource.
926 if (fontTypeface == null) {
927 fontFamily = appearance.getString(attr);
931 case com.android.internal.R.styleable.TextAppearance_textStyle:
932 styleIndex = appearance.getInt(attr, -1);
935 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
936 allCaps = appearance.getBoolean(attr, false);
939 case com.android.internal.R.styleable.TextAppearance_shadowColor:
940 shadowcolor = appearance.getInt(attr, 0);
943 case com.android.internal.R.styleable.TextAppearance_shadowDx:
944 dx = appearance.getFloat(attr, 0);
947 case com.android.internal.R.styleable.TextAppearance_shadowDy:
948 dy = appearance.getFloat(attr, 0);
951 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
952 r = appearance.getFloat(attr, 0);
955 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
956 elegant = appearance.getBoolean(attr, false);
959 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
960 letterSpacing = appearance.getFloat(attr, 0);
963 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
964 fontFeatureSettings = appearance.getString(attr);
969 appearance.recycle();
972 boolean editable = getDefaultEditable();
973 CharSequence inputMethod = null;
975 CharSequence digits = null;
976 boolean phone = false;
977 boolean autotext = false;
980 boolean selectallonfocus = false;
981 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
982 drawableBottom = null, drawableStart = null, drawableEnd = null;
983 ColorStateList drawableTint = null;
984 PorterDuff.Mode drawableTintMode = null;
985 int drawablePadding = 0;
987 boolean singleLine = false;
989 CharSequence text = "";
990 CharSequence hint = null;
991 boolean password = false;
992 float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
993 float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
994 float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
995 int inputType = EditorInfo.TYPE_NULL;
996 a = theme.obtainStyledAttributes(
997 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
999 int n = a.getIndexCount();
1001 boolean fromResourceId = false;
1002 for (int i = 0; i < n; i++) {
1003 int attr = a.getIndex(i);
1006 case com.android.internal.R.styleable.TextView_editable:
1007 editable = a.getBoolean(attr, editable);
1010 case com.android.internal.R.styleable.TextView_inputMethod:
1011 inputMethod = a.getText(attr);
1014 case com.android.internal.R.styleable.TextView_numeric:
1015 numeric = a.getInt(attr, numeric);
1018 case com.android.internal.R.styleable.TextView_digits:
1019 digits = a.getText(attr);
1022 case com.android.internal.R.styleable.TextView_phoneNumber:
1023 phone = a.getBoolean(attr, phone);
1026 case com.android.internal.R.styleable.TextView_autoText:
1027 autotext = a.getBoolean(attr, autotext);
1030 case com.android.internal.R.styleable.TextView_capitalize:
1031 autocap = a.getInt(attr, autocap);
1034 case com.android.internal.R.styleable.TextView_bufferType:
1035 buffertype = a.getInt(attr, buffertype);
1038 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
1039 selectallonfocus = a.getBoolean(attr, selectallonfocus);
1042 case com.android.internal.R.styleable.TextView_autoLink:
1043 mAutoLinkMask = a.getInt(attr, 0);
1046 case com.android.internal.R.styleable.TextView_linksClickable:
1047 mLinksClickable = a.getBoolean(attr, true);
1050 case com.android.internal.R.styleable.TextView_drawableLeft:
1051 drawableLeft = a.getDrawable(attr);
1054 case com.android.internal.R.styleable.TextView_drawableTop:
1055 drawableTop = a.getDrawable(attr);
1058 case com.android.internal.R.styleable.TextView_drawableRight:
1059 drawableRight = a.getDrawable(attr);
1062 case com.android.internal.R.styleable.TextView_drawableBottom:
1063 drawableBottom = a.getDrawable(attr);
1066 case com.android.internal.R.styleable.TextView_drawableStart:
1067 drawableStart = a.getDrawable(attr);
1070 case com.android.internal.R.styleable.TextView_drawableEnd:
1071 drawableEnd = a.getDrawable(attr);
1074 case com.android.internal.R.styleable.TextView_drawableTint:
1075 drawableTint = a.getColorStateList(attr);
1078 case com.android.internal.R.styleable.TextView_drawableTintMode:
1079 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
1082 case com.android.internal.R.styleable.TextView_drawablePadding:
1083 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
1086 case com.android.internal.R.styleable.TextView_maxLines:
1087 setMaxLines(a.getInt(attr, -1));
1090 case com.android.internal.R.styleable.TextView_maxHeight:
1091 setMaxHeight(a.getDimensionPixelSize(attr, -1));
1094 case com.android.internal.R.styleable.TextView_lines:
1095 setLines(a.getInt(attr, -1));
1098 case com.android.internal.R.styleable.TextView_height:
1099 setHeight(a.getDimensionPixelSize(attr, -1));
1102 case com.android.internal.R.styleable.TextView_minLines:
1103 setMinLines(a.getInt(attr, -1));
1106 case com.android.internal.R.styleable.TextView_minHeight:
1107 setMinHeight(a.getDimensionPixelSize(attr, -1));
1110 case com.android.internal.R.styleable.TextView_maxEms:
1111 setMaxEms(a.getInt(attr, -1));
1114 case com.android.internal.R.styleable.TextView_maxWidth:
1115 setMaxWidth(a.getDimensionPixelSize(attr, -1));
1118 case com.android.internal.R.styleable.TextView_ems:
1119 setEms(a.getInt(attr, -1));
1122 case com.android.internal.R.styleable.TextView_width:
1123 setWidth(a.getDimensionPixelSize(attr, -1));
1126 case com.android.internal.R.styleable.TextView_minEms:
1127 setMinEms(a.getInt(attr, -1));
1130 case com.android.internal.R.styleable.TextView_minWidth:
1131 setMinWidth(a.getDimensionPixelSize(attr, -1));
1134 case com.android.internal.R.styleable.TextView_gravity:
1135 setGravity(a.getInt(attr, -1));
1138 case com.android.internal.R.styleable.TextView_hint:
1139 hint = a.getText(attr);
1142 case com.android.internal.R.styleable.TextView_text:
1143 fromResourceId = true;
1144 text = a.getText(attr);
1147 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1148 if (a.getBoolean(attr, false)) {
1149 setHorizontallyScrolling(true);
1153 case com.android.internal.R.styleable.TextView_singleLine:
1154 singleLine = a.getBoolean(attr, singleLine);
1157 case com.android.internal.R.styleable.TextView_ellipsize:
1158 ellipsize = a.getInt(attr, ellipsize);
1161 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1162 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1165 case com.android.internal.R.styleable.TextView_includeFontPadding:
1166 if (!a.getBoolean(attr, true)) {
1167 setIncludeFontPadding(false);
1171 case com.android.internal.R.styleable.TextView_cursorVisible:
1172 if (!a.getBoolean(attr, true)) {
1173 setCursorVisible(false);
1177 case com.android.internal.R.styleable.TextView_maxLength:
1178 maxlength = a.getInt(attr, -1);
1181 case com.android.internal.R.styleable.TextView_textScaleX:
1182 setTextScaleX(a.getFloat(attr, 1.0f));
1185 case com.android.internal.R.styleable.TextView_freezesText:
1186 mFreezesText = a.getBoolean(attr, false);
1189 case com.android.internal.R.styleable.TextView_shadowColor:
1190 shadowcolor = a.getInt(attr, 0);
1193 case com.android.internal.R.styleable.TextView_shadowDx:
1194 dx = a.getFloat(attr, 0);
1197 case com.android.internal.R.styleable.TextView_shadowDy:
1198 dy = a.getFloat(attr, 0);
1201 case com.android.internal.R.styleable.TextView_shadowRadius:
1202 r = a.getFloat(attr, 0);
1205 case com.android.internal.R.styleable.TextView_enabled:
1206 setEnabled(a.getBoolean(attr, isEnabled()));
1209 case com.android.internal.R.styleable.TextView_textColorHighlight:
1210 textColorHighlight = a.getColor(attr, textColorHighlight);
1213 case com.android.internal.R.styleable.TextView_textColor:
1214 textColor = a.getColorStateList(attr);
1217 case com.android.internal.R.styleable.TextView_textColorHint:
1218 textColorHint = a.getColorStateList(attr);
1221 case com.android.internal.R.styleable.TextView_textColorLink:
1222 textColorLink = a.getColorStateList(attr);
1225 case com.android.internal.R.styleable.TextView_textSize:
1226 textSize = a.getDimensionPixelSize(attr, textSize);
1229 case com.android.internal.R.styleable.TextView_typeface:
1230 typefaceIndex = a.getInt(attr, typefaceIndex);
1233 case com.android.internal.R.styleable.TextView_textStyle:
1234 styleIndex = a.getInt(attr, styleIndex);
1237 case com.android.internal.R.styleable.TextView_fontFamily:
1238 if (!context.isRestricted() && context.canLoadUnsafeResources()) {
1240 fontTypeface = a.getFont(attr);
1241 } catch (UnsupportedOperationException | Resources.NotFoundException e) {
1242 // Expected if it is not a resource reference or if it is a reference to
1243 // another resource type.
1246 if (fontTypeface == null) {
1247 fontFamily = a.getString(attr);
1249 fontFamilyExplicit = true;
1252 case com.android.internal.R.styleable.TextView_password:
1253 password = a.getBoolean(attr, password);
1256 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1257 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1260 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1261 mSpacingMult = a.getFloat(attr, mSpacingMult);
1264 case com.android.internal.R.styleable.TextView_inputType:
1265 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1268 case com.android.internal.R.styleable.TextView_allowUndo:
1269 createEditorIfNeeded();
1270 mEditor.mAllowUndo = a.getBoolean(attr, true);
1273 case com.android.internal.R.styleable.TextView_imeOptions:
1274 createEditorIfNeeded();
1275 mEditor.createInputContentTypeIfNeeded();
1276 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1277 mEditor.mInputContentType.imeOptions);
1280 case com.android.internal.R.styleable.TextView_imeActionLabel:
1281 createEditorIfNeeded();
1282 mEditor.createInputContentTypeIfNeeded();
1283 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1286 case com.android.internal.R.styleable.TextView_imeActionId:
1287 createEditorIfNeeded();
1288 mEditor.createInputContentTypeIfNeeded();
1289 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1290 mEditor.mInputContentType.imeActionId);
1293 case com.android.internal.R.styleable.TextView_privateImeOptions:
1294 setPrivateImeOptions(a.getString(attr));
1297 case com.android.internal.R.styleable.TextView_editorExtras:
1299 setInputExtras(a.getResourceId(attr, 0));
1300 } catch (XmlPullParserException e) {
1301 Log.w(LOG_TAG, "Failure reading input extras", e);
1302 } catch (IOException e) {
1303 Log.w(LOG_TAG, "Failure reading input extras", e);
1307 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1308 mCursorDrawableRes = a.getResourceId(attr, 0);
1311 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1312 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1315 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1316 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1319 case com.android.internal.R.styleable.TextView_textSelectHandle:
1320 mTextSelectHandleRes = a.getResourceId(attr, 0);
1323 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1324 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1327 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1328 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1331 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1332 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1335 case com.android.internal.R.styleable.TextView_textIsSelectable:
1336 setTextIsSelectable(a.getBoolean(attr, false));
1339 case com.android.internal.R.styleable.TextView_textAllCaps:
1340 allCaps = a.getBoolean(attr, false);
1343 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1344 elegant = a.getBoolean(attr, false);
1347 case com.android.internal.R.styleable.TextView_letterSpacing:
1348 letterSpacing = a.getFloat(attr, 0);
1351 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1352 fontFeatureSettings = a.getString(attr);
1355 case com.android.internal.R.styleable.TextView_breakStrategy:
1356 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1359 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1360 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1363 case com.android.internal.R.styleable.TextView_autoSizeTextType:
1364 mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
1367 case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
1368 autoSizeStepGranularityInPx = a.getDimension(attr,
1369 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1372 case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
1373 autoSizeMinTextSizeInPx = a.getDimension(attr,
1374 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1377 case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize:
1378 autoSizeMaxTextSizeInPx = a.getDimension(attr,
1379 UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE);
1382 case com.android.internal.R.styleable.TextView_autoSizePresetSizes:
1383 final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0);
1384 if (autoSizeStepSizeArrayResId > 0) {
1385 final TypedArray autoSizePresetTextSizes = a.getResources()
1386 .obtainTypedArray(autoSizeStepSizeArrayResId);
1387 setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes);
1388 autoSizePresetTextSizes.recycle();
1391 case com.android.internal.R.styleable.TextView_justificationMode:
1392 mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
1399 BufferType bufferType = BufferType.EDITABLE;
1401 final int variation =
1402 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1403 final boolean passwordInputType = variation
1404 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1405 final boolean webPasswordInputType = variation
1406 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1407 final boolean numberPasswordInputType = variation
1408 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1410 mUseInternationalizedInput =
1411 context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.O;
1413 if (inputMethod != null) {
1417 c = Class.forName(inputMethod.toString());
1418 } catch (ClassNotFoundException ex) {
1419 throw new RuntimeException(ex);
1423 createEditorIfNeeded();
1424 mEditor.mKeyListener = (KeyListener) c.newInstance();
1425 } catch (InstantiationException ex) {
1426 throw new RuntimeException(ex);
1427 } catch (IllegalAccessException ex) {
1428 throw new RuntimeException(ex);
1431 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1433 : mEditor.mKeyListener.getInputType();
1434 } catch (IncompatibleClassChangeError e) {
1435 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1437 } else if (digits != null) {
1438 createEditorIfNeeded();
1439 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1440 // If no input type was specified, we will default to generic
1441 // text, since we can't tell the IME about the set of digits
1442 // that was selected.
1443 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1444 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1445 } else if (inputType != EditorInfo.TYPE_NULL) {
1446 setInputType(inputType, true);
1447 // If set, the input type overrides what was set using the deprecated singleLine flag.
1448 singleLine = !isMultilineInputType(inputType);
1450 createEditorIfNeeded();
1451 mEditor.mKeyListener = DialerKeyListener.getInstance();
1452 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1453 } else if (numeric != 0) {
1454 createEditorIfNeeded();
1455 mEditor.mKeyListener = DigitsKeyListener.getInstance(
1457 (numeric & SIGNED) != 0,
1458 (numeric & DECIMAL) != 0);
1459 inputType = mEditor.mKeyListener.getInputType();
1460 mEditor.mInputType = inputType;
1461 } else if (autotext || autocap != -1) {
1462 TextKeyListener.Capitalize cap;
1464 inputType = EditorInfo.TYPE_CLASS_TEXT;
1468 cap = TextKeyListener.Capitalize.SENTENCES;
1469 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1473 cap = TextKeyListener.Capitalize.WORDS;
1474 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1478 cap = TextKeyListener.Capitalize.CHARACTERS;
1479 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1483 cap = TextKeyListener.Capitalize.NONE;
1487 createEditorIfNeeded();
1488 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1489 mEditor.mInputType = inputType;
1490 } else if (editable) {
1491 createEditorIfNeeded();
1492 mEditor.mKeyListener = TextKeyListener.getInstance();
1493 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1494 } else if (isTextSelectable()) {
1495 // Prevent text changes from keyboard.
1496 if (mEditor != null) {
1497 mEditor.mKeyListener = null;
1498 mEditor.mInputType = EditorInfo.TYPE_NULL;
1500 bufferType = BufferType.SPANNABLE;
1501 // So that selection can be changed using arrow keys and touch is handled.
1502 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1504 if (mEditor != null) mEditor.mKeyListener = null;
1506 switch (buffertype) {
1508 bufferType = BufferType.NORMAL;
1511 bufferType = BufferType.SPANNABLE;
1514 bufferType = BufferType.EDITABLE;
1519 if (mEditor != null) {
1520 mEditor.adjustInputType(password, passwordInputType, webPasswordInputType,
1521 numberPasswordInputType);
1524 if (selectallonfocus) {
1525 createEditorIfNeeded();
1526 mEditor.mSelectAllOnFocus = true;
1528 if (bufferType == BufferType.NORMAL) {
1529 bufferType = BufferType.SPANNABLE;
1533 // Set up the tint (if needed) before setting the drawables so that it
1534 // gets applied correctly.
1535 if (drawableTint != null || drawableTintMode != null) {
1536 if (mDrawables == null) {
1537 mDrawables = new Drawables(context);
1539 if (drawableTint != null) {
1540 mDrawables.mTintList = drawableTint;
1541 mDrawables.mHasTint = true;
1543 if (drawableTintMode != null) {
1544 mDrawables.mTintMode = drawableTintMode;
1545 mDrawables.mHasTintMode = true;
1549 // This call will save the initial left/right drawables
1550 setCompoundDrawablesWithIntrinsicBounds(
1551 drawableLeft, drawableTop, drawableRight, drawableBottom);
1552 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1553 setCompoundDrawablePadding(drawablePadding);
1555 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1556 // of lines of height are unchanged for multi-line TextViews.
1557 setInputTypeSingleLine(singleLine);
1558 applySingleLine(singleLine, singleLine, singleLine);
1560 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1561 ellipsize = 3; // END
1564 switch (ellipsize) {
1566 setEllipsize(TextUtils.TruncateAt.START);
1569 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1572 setEllipsize(TextUtils.TruncateAt.END);
1575 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1576 setHorizontalFadingEdgeEnabled(true);
1577 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1579 setHorizontalFadingEdgeEnabled(false);
1580 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1582 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1586 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1587 setHintTextColor(textColorHint);
1588 setLinkTextColor(textColorLink);
1589 if (textColorHighlight != 0) {
1590 setHighlightColor(textColorHighlight);
1592 setRawTextSize(textSize, true /* shouldRequestLayout */);
1593 setElegantTextHeight(elegant);
1594 setLetterSpacing(letterSpacing);
1595 setFontFeatureSettings(fontFeatureSettings);
1598 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1601 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1602 setTransformationMethod(PasswordTransformationMethod.getInstance());
1603 typefaceIndex = MONOSPACE;
1604 } else if (mEditor != null
1605 && (mEditor.mInputType
1606 & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1607 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1608 typefaceIndex = MONOSPACE;
1611 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1614 setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
1616 if (shadowcolor != 0) {
1617 setShadowLayer(r, dx, dy, shadowcolor);
1620 if (maxlength >= 0) {
1621 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1623 setFilters(NO_FILTERS);
1626 setText(text, bufferType);
1627 if (fromResourceId) {
1628 mTextFromResource = true;
1631 if (hint != null) setHint(hint);
1634 * Views are not normally clickable unless specified to be.
1635 * However, TextViews that have input or movement methods *are*
1636 * clickable by default. By setting clickable here, we implicitly set focusable as well
1637 * if not overridden by the developer.
1639 a = context.obtainStyledAttributes(
1640 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1641 boolean canInputOrMove = (mMovement != null || getKeyListener() != null);
1642 boolean clickable = canInputOrMove || isClickable();
1643 boolean longClickable = canInputOrMove || isLongClickable();
1644 int focusable = getFocusable();
1646 n = a.getIndexCount();
1647 for (int i = 0; i < n; i++) {
1648 int attr = a.getIndex(i);
1651 case com.android.internal.R.styleable.View_focusable:
1652 TypedValue val = new TypedValue();
1653 if (a.getValue(attr, val)) {
1654 focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN)
1655 ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE)
1660 case com.android.internal.R.styleable.View_clickable:
1661 clickable = a.getBoolean(attr, clickable);
1664 case com.android.internal.R.styleable.View_longClickable:
1665 longClickable = a.getBoolean(attr, longClickable);
1671 // Some apps were relying on the undefined behavior of focusable winning over
1672 // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually
1673 // when starting with EditText and setting only focusable=false). To keep those apps from
1674 // breaking, re-apply the focusable attribute here.
1675 if (focusable != getFocusable()) {
1676 setFocusable(focusable);
1678 setClickable(clickable);
1679 setLongClickable(longClickable);
1681 if (mEditor != null) mEditor.prepareCursorControllers();
1683 // If not explicitly specified this view is important for accessibility.
1684 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1685 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1688 if (supportsAutoSizeText()) {
1689 if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
1690 // If uniform auto-size has been specified but preset values have not been set then
1691 // replace the auto-size configuration values that have not been specified with the
1693 if (!mHasPresetAutoSizeValues) {
1694 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1696 if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1697 autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1698 TypedValue.COMPLEX_UNIT_SP,
1699 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1703 if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1704 autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1705 TypedValue.COMPLEX_UNIT_SP,
1706 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1710 if (autoSizeStepGranularityInPx
1711 == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
1712 autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
1715 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1716 autoSizeMaxTextSizeInPx,
1717 autoSizeStepGranularityInPx);
1720 setupAutoSizeText();
1723 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
1728 * Specify whether this widget should automatically scale the text to try to perfectly fit
1729 * within the layout bounds by using the default auto-size configuration.
1731 * @param autoSizeTextType the type of auto-size. Must be one of
1732 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1733 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1735 * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above.
1737 * @attr ref android.R.styleable#TextView_autoSizeTextType
1739 * @see #getAutoSizeTextType()
1741 public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) {
1742 if (supportsAutoSizeText()) {
1743 switch (autoSizeTextType) {
1744 case AUTO_SIZE_TEXT_TYPE_NONE:
1745 clearAutoSizeConfiguration();
1747 case AUTO_SIZE_TEXT_TYPE_UNIFORM:
1748 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1749 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1750 TypedValue.COMPLEX_UNIT_SP,
1751 DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP,
1753 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1754 TypedValue.COMPLEX_UNIT_SP,
1755 DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP,
1758 validateAndSetAutoSizeTextTypeUniformConfiguration(
1759 autoSizeMinTextSizeInPx,
1760 autoSizeMaxTextSizeInPx,
1761 DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
1762 if (setupAutoSizeText()) {
1768 throw new IllegalArgumentException(
1769 "Unknown auto-size text type: " + autoSizeTextType);
1775 * Specify whether this widget should automatically scale the text to try to perfectly fit
1776 * within the layout bounds. If all the configuration params are valid the type of auto-size is
1777 * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1779 * @param autoSizeMinTextSize the minimum text size available for auto-size
1780 * @param autoSizeMaxTextSize the maximum text size available for auto-size
1781 * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with
1782 * the minimum and maximum text size in order to build the set of
1783 * text sizes the system uses to choose from when auto-sizing
1784 * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the
1785 * possible dimension units
1787 * @throws IllegalArgumentException if any of the configuration params are invalid.
1789 * @attr ref android.R.styleable#TextView_autoSizeTextType
1790 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1791 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1792 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1794 * @see #setAutoSizeTextTypeWithDefaults(int)
1795 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1796 * @see #getAutoSizeMinTextSize()
1797 * @see #getAutoSizeMaxTextSize()
1798 * @see #getAutoSizeStepGranularity()
1799 * @see #getAutoSizeTextAvailableSizes()
1801 public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize,
1802 int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) {
1803 if (supportsAutoSizeText()) {
1804 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1805 final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(
1806 unit, autoSizeMinTextSize, displayMetrics);
1807 final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(
1808 unit, autoSizeMaxTextSize, displayMetrics);
1809 final float autoSizeStepGranularityInPx = TypedValue.applyDimension(
1810 unit, autoSizeStepGranularity, displayMetrics);
1812 validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx,
1813 autoSizeMaxTextSizeInPx,
1814 autoSizeStepGranularityInPx);
1816 if (setupAutoSizeText()) {
1824 * Specify whether this widget should automatically scale the text to try to perfectly fit
1825 * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid
1826 * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}.
1828 * @param presetSizes an {@code int} array of sizes in pixels
1829 * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for
1830 * the possible dimension units
1832 * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid.
1834 * @attr ref android.R.styleable#TextView_autoSizeTextType
1835 * @attr ref android.R.styleable#TextView_autoSizePresetSizes
1837 * @see #setAutoSizeTextTypeWithDefaults(int)
1838 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1839 * @see #getAutoSizeMinTextSize()
1840 * @see #getAutoSizeMaxTextSize()
1841 * @see #getAutoSizeTextAvailableSizes()
1843 public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) {
1844 if (supportsAutoSizeText()) {
1845 final int presetSizesLength = presetSizes.length;
1846 if (presetSizesLength > 0) {
1847 int[] presetSizesInPx = new int[presetSizesLength];
1849 if (unit == TypedValue.COMPLEX_UNIT_PX) {
1850 presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength);
1852 final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
1853 // Convert all to sizes to pixels.
1854 for (int i = 0; i < presetSizesLength; i++) {
1855 presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit,
1856 presetSizes[i], displayMetrics));
1860 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx);
1861 if (!setupAutoSizeUniformPresetSizesConfiguration()) {
1862 throw new IllegalArgumentException("None of the preset sizes is valid: "
1863 + Arrays.toString(presetSizes));
1866 mHasPresetAutoSizeValues = false;
1869 if (setupAutoSizeText()) {
1877 * Returns the type of auto-size set for this widget.
1879 * @return an {@code int} corresponding to one of the auto-size types:
1880 * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or
1881 * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM}
1883 * @attr ref android.R.styleable#TextView_autoSizeTextType
1885 * @see #setAutoSizeTextTypeWithDefaults(int)
1886 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1887 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1890 public int getAutoSizeTextType() {
1891 return mAutoSizeTextType;
1895 * @return the current auto-size step granularity in pixels.
1897 * @attr ref android.R.styleable#TextView_autoSizeStepGranularity
1899 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1901 public int getAutoSizeStepGranularity() {
1902 return Math.round(mAutoSizeStepGranularityInPx);
1906 * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that
1907 * if auto-size has not been configured this function returns {@code -1}.
1909 * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
1911 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1912 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1914 public int getAutoSizeMinTextSize() {
1915 return Math.round(mAutoSizeMinTextSizeInPx);
1919 * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that
1920 * if auto-size has not been configured this function returns {@code -1}.
1922 * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
1924 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1925 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1927 public int getAutoSizeMaxTextSize() {
1928 return Math.round(mAutoSizeMaxTextSizeInPx);
1932 * @return the current auto-size {@code int} sizes array (in pixels).
1934 * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int)
1935 * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int)
1937 public int[] getAutoSizeTextAvailableSizes() {
1938 return mAutoSizeTextSizesInPx;
1941 private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) {
1942 final int textSizesLength = textSizes.length();
1943 final int[] parsedSizes = new int[textSizesLength];
1945 if (textSizesLength > 0) {
1946 for (int i = 0; i < textSizesLength; i++) {
1947 parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1);
1949 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes);
1950 setupAutoSizeUniformPresetSizesConfiguration();
1954 private boolean setupAutoSizeUniformPresetSizesConfiguration() {
1955 final int sizesLength = mAutoSizeTextSizesInPx.length;
1956 mHasPresetAutoSizeValues = sizesLength > 0;
1957 if (mHasPresetAutoSizeValues) {
1958 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1959 mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0];
1960 mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1];
1961 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
1963 return mHasPresetAutoSizeValues;
1967 * If all params are valid then save the auto-size configuration.
1969 * @throws IllegalArgumentException if any of the params are invalid
1971 private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx,
1972 float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) {
1974 if (autoSizeMinTextSizeInPx <= 0) {
1975 throw new IllegalArgumentException("Minimum auto-size text size ("
1976 + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)");
1979 if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) {
1980 throw new IllegalArgumentException("Maximum auto-size text size ("
1981 + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size "
1982 + "text size (" + autoSizeMinTextSizeInPx + "px)");
1985 if (autoSizeStepGranularityInPx <= 0) {
1986 throw new IllegalArgumentException("The auto-size step granularity ("
1987 + autoSizeStepGranularityInPx + "px) is less or equal to (0px)");
1990 // All good, persist the configuration.
1991 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM;
1992 mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx;
1993 mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx;
1994 mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx;
1995 mHasPresetAutoSizeValues = false;
1998 private void clearAutoSizeConfiguration() {
1999 mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
2000 mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2001 mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2002 mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE;
2003 mAutoSizeTextSizesInPx = EmptyArray.INT;
2004 mNeedsAutoSizeText = false;
2007 // Returns distinct sorted positive values.
2008 private int[] cleanupAutoSizePresetSizes(int[] presetValues) {
2009 final int presetValuesLength = presetValues.length;
2010 if (presetValuesLength == 0) {
2011 return presetValues;
2013 Arrays.sort(presetValues);
2015 final IntArray uniqueValidSizes = new IntArray();
2016 for (int i = 0; i < presetValuesLength; i++) {
2017 final int currentPresetValue = presetValues[i];
2019 if (currentPresetValue > 0
2020 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) {
2021 uniqueValidSizes.add(currentPresetValue);
2025 return presetValuesLength == uniqueValidSizes.size()
2027 : uniqueValidSizes.toArray();
2030 private boolean setupAutoSizeText() {
2031 if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) {
2032 // Calculate the sizes set based on minimum size, maximum size and step size if we do
2033 // not have a predefined set of sizes or if the current sizes array is empty.
2034 if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) {
2035 int autoSizeValuesLength = 1;
2036 float currentSize = Math.round(mAutoSizeMinTextSizeInPx);
2037 while (Math.round(currentSize + mAutoSizeStepGranularityInPx)
2038 <= Math.round(mAutoSizeMaxTextSizeInPx)) {
2039 autoSizeValuesLength++;
2040 currentSize += mAutoSizeStepGranularityInPx;
2043 int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength];
2044 float sizeToAdd = mAutoSizeMinTextSizeInPx;
2045 for (int i = 0; i < autoSizeValuesLength; i++) {
2046 autoSizeTextSizesInPx[i] = Math.round(sizeToAdd);
2047 sizeToAdd += mAutoSizeStepGranularityInPx;
2049 mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx);
2052 mNeedsAutoSizeText = true;
2054 mNeedsAutoSizeText = false;
2057 return mNeedsAutoSizeText;
2060 private int[] parseDimensionArray(TypedArray dimens) {
2061 if (dimens == null) {
2064 int[] result = new int[dimens.length()];
2065 for (int i = 0; i < result.length; i++) {
2066 result[i] = dimens.getDimensionPixelSize(i, 0);
2075 public void onActivityResult(int requestCode, int resultCode, Intent data) {
2076 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
2077 if (resultCode == Activity.RESULT_OK && data != null) {
2078 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
2079 if (result != null) {
2080 if (isTextEditable()) {
2081 replaceSelectionWithText(result);
2082 if (mEditor != null) {
2083 mEditor.refreshTextActionMode();
2086 if (result.length() > 0) {
2087 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
2092 } else if (mText instanceof Spannable) {
2093 // Reset the selection.
2094 Selection.setSelection((Spannable) mText, getSelectionEnd());
2099 private void setTypefaceFromAttrs(Typeface fontTypeface, String familyName, int typefaceIndex,
2101 Typeface tf = fontTypeface;
2102 if (tf == null && familyName != null) {
2103 tf = Typeface.create(familyName, styleIndex);
2104 } else if (tf != null && tf.getStyle() != styleIndex) {
2105 tf = Typeface.create(tf, styleIndex);
2111 switch (typefaceIndex) {
2113 tf = Typeface.SANS_SERIF;
2117 tf = Typeface.SERIF;
2121 tf = Typeface.MONOSPACE;
2125 setTypeface(tf, styleIndex);
2128 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
2129 boolean hasRelativeDrawables = (start != null) || (end != null);
2130 if (hasRelativeDrawables) {
2131 Drawables dr = mDrawables;
2133 mDrawables = dr = new Drawables(getContext());
2135 mDrawables.mOverride = true;
2136 final Rect compoundRect = dr.mCompoundRect;
2137 int[] state = getDrawableState();
2138 if (start != null) {
2139 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2140 start.setState(state);
2141 start.copyBounds(compoundRect);
2142 start.setCallback(this);
2144 dr.mDrawableStart = start;
2145 dr.mDrawableSizeStart = compoundRect.width();
2146 dr.mDrawableHeightStart = compoundRect.height();
2148 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2151 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2152 end.setState(state);
2153 end.copyBounds(compoundRect);
2154 end.setCallback(this);
2156 dr.mDrawableEnd = end;
2157 dr.mDrawableSizeEnd = compoundRect.width();
2158 dr.mDrawableHeightEnd = compoundRect.height();
2160 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2162 resetResolvedDrawables();
2164 applyCompoundDrawableTint();
2168 @android.view.RemotableViewMethod
2170 public void setEnabled(boolean enabled) {
2171 if (enabled == isEnabled()) {
2176 // Hide the soft input if the currently active TextView is disabled
2177 InputMethodManager imm = InputMethodManager.peekInstance();
2178 if (imm != null && imm.isActive(this)) {
2179 imm.hideSoftInputFromWindow(getWindowToken(), 0);
2183 super.setEnabled(enabled);
2186 // Make sure IME is updated with current editor info.
2187 InputMethodManager imm = InputMethodManager.peekInstance();
2188 if (imm != null) imm.restartInput(this);
2191 // Will change text color
2192 if (mEditor != null) {
2193 mEditor.invalidateTextDisplayList();
2194 mEditor.prepareCursorControllers();
2196 // start or stop the cursor blinking as appropriate
2197 mEditor.makeBlink();
2202 * Sets the typeface and style in which the text should be displayed,
2203 * and turns on the fake bold and italic bits in the Paint if the
2204 * Typeface that you provided does not have all the bits in the
2205 * style that you specified.
2207 * @attr ref android.R.styleable#TextView_typeface
2208 * @attr ref android.R.styleable#TextView_textStyle
2210 public void setTypeface(Typeface tf, int style) {
2213 tf = Typeface.defaultFromStyle(style);
2215 tf = Typeface.create(tf, style);
2219 // now compute what (if any) algorithmic styling is needed
2220 int typefaceStyle = tf != null ? tf.getStyle() : 0;
2221 int need = style & ~typefaceStyle;
2222 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
2223 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
2225 mTextPaint.setFakeBoldText(false);
2226 mTextPaint.setTextSkewX(0);
2232 * Subclasses override this to specify that they have a KeyListener
2233 * by default even if not specifically called for in the XML options.
2235 protected boolean getDefaultEditable() {
2240 * Subclasses override this to specify a default movement method.
2242 protected MovementMethod getDefaultMovementMethod() {
2247 * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called
2248 * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE}
2249 * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast
2250 * the return value from this method to Spannable or Editable, respectively.
2252 * <p>The content of the return value should not be modified. If you want a modifiable one, you
2253 * should make your own copy first.</p>
2255 * @return The text displayed by the text view.
2256 * @attr ref android.R.styleable#TextView_text
2258 @ViewDebug.CapturedViewProperty
2259 public CharSequence getText() {
2264 * Returns the length, in characters, of the text managed by this TextView
2265 * @return The length of the text managed by the TextView in characters.
2267 public int length() {
2268 return mText.length();
2272 * Return the text that TextView is displaying as an Editable object. If the text is not
2273 * editable, null is returned.
2277 public Editable getEditableText() {
2278 return (mText instanceof Editable) ? (Editable) mText : null;
2282 * Gets the vertical distance between lines of text, in pixels.
2283 * Note that markup within the text can cause individual lines
2284 * to be taller or shorter than this height, and the layout may
2285 * contain additional first-or last-line padding.
2286 * @return The height of one standard line in pixels.
2288 public int getLineHeight() {
2289 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
2293 * Gets the {@link android.text.Layout} that is currently being used to display the text.
2294 * This value can be null if the text or width has recently changed.
2295 * @return The Layout that is currently being used to display the text.
2297 public final Layout getLayout() {
2302 * @return the {@link android.text.Layout} that is currently being used to
2303 * display the hint text. This can be null.
2305 final Layout getHintLayout() {
2310 * Retrieve the {@link android.content.UndoManager} that is currently associated
2311 * with this TextView. By default there is no associated UndoManager, so null
2312 * is returned. One can be associated with the TextView through
2313 * {@link #setUndoManager(android.content.UndoManager, String)}
2317 public final UndoManager getUndoManager() {
2318 // TODO: Consider supporting a global undo manager.
2319 throw new UnsupportedOperationException("not implemented");
2327 public final Editor getEditorForTesting() {
2332 * Associate an {@link android.content.UndoManager} with this TextView. Once
2333 * done, all edit operations on the TextView will result in appropriate
2334 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
2337 * @param undoManager The {@link android.content.UndoManager} to associate with
2338 * this TextView, or null to clear any existing association.
2339 * @param tag String tag identifying this particular TextView owner in the
2340 * UndoManager. This is used to keep the correct association with the
2341 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
2345 public final void setUndoManager(UndoManager undoManager, String tag) {
2346 // TODO: Consider supporting a global undo manager. An implementation will need to:
2347 // * createEditorIfNeeded()
2348 // * Promote to BufferType.EDITABLE if needed.
2349 // * Update the UndoManager and UndoOwner.
2350 // Likewise it will need to be able to restore the default UndoManager.
2351 throw new UnsupportedOperationException("not implemented");
2355 * Gets the current {@link KeyListener} for the TextView.
2356 * This will frequently be null for non-EditText TextViews.
2357 * @return the current key listener for this TextView.
2359 * @attr ref android.R.styleable#TextView_numeric
2360 * @attr ref android.R.styleable#TextView_digits
2361 * @attr ref android.R.styleable#TextView_phoneNumber
2362 * @attr ref android.R.styleable#TextView_inputMethod
2363 * @attr ref android.R.styleable#TextView_capitalize
2364 * @attr ref android.R.styleable#TextView_autoText
2366 public final KeyListener getKeyListener() {
2367 return mEditor == null ? null : mEditor.mKeyListener;
2371 * Sets the key listener to be used with this TextView. This can be null
2372 * to disallow user input. Note that this method has significant and
2373 * subtle interactions with soft keyboards and other input method:
2374 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
2375 * for important details. Calling this method will replace the current
2376 * content type of the text view with the content type returned by the
2379 * Be warned that if you want a TextView with a key listener or movement
2380 * method not to be focusable, or if you want a TextView without a
2381 * key listener or movement method to be focusable, you must call
2382 * {@link #setFocusable} again after calling this to get the focusability
2383 * back the way you want it.
2385 * @attr ref android.R.styleable#TextView_numeric
2386 * @attr ref android.R.styleable#TextView_digits
2387 * @attr ref android.R.styleable#TextView_phoneNumber
2388 * @attr ref android.R.styleable#TextView_inputMethod
2389 * @attr ref android.R.styleable#TextView_capitalize
2390 * @attr ref android.R.styleable#TextView_autoText
2392 public void setKeyListener(KeyListener input) {
2393 mListenerChanged = true;
2394 setKeyListenerOnly(input);
2395 fixFocusableAndClickableSettings();
2397 if (input != null) {
2398 createEditorIfNeeded();
2399 setInputTypeFromEditor();
2401 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
2404 InputMethodManager imm = InputMethodManager.peekInstance();
2405 if (imm != null) imm.restartInput(this);
2408 private void setInputTypeFromEditor() {
2410 mEditor.mInputType = mEditor.mKeyListener.getInputType();
2411 } catch (IncompatibleClassChangeError e) {
2412 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
2414 // Change inputType, without affecting transformation.
2415 // No need to applySingleLine since mSingleLine is unchanged.
2416 setInputTypeSingleLine(mSingleLine);
2419 private void setKeyListenerOnly(KeyListener input) {
2420 if (mEditor == null && input == null) return; // null is the default value
2422 createEditorIfNeeded();
2423 if (mEditor.mKeyListener != input) {
2424 mEditor.mKeyListener = input;
2425 if (input != null && !(mText instanceof Editable)) {
2429 setFilters((Editable) mText, mFilters);
2434 * Gets the {@link android.text.method.MovementMethod} being used for this TextView,
2435 * which provides positioning, scrolling, and text selection functionality.
2436 * This will frequently be null for non-EditText TextViews.
2437 * @return the movement method being used for this TextView.
2438 * @see android.text.method.MovementMethod
2440 public final MovementMethod getMovementMethod() {
2445 * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement
2446 * for this TextView. This can be null to disallow using the arrow keys to move the
2447 * cursor or scroll the view.
2449 * Be warned that if you want a TextView with a key listener or movement
2450 * method not to be focusable, or if you want a TextView without a
2451 * key listener or movement method to be focusable, you must call
2452 * {@link #setFocusable} again after calling this to get the focusability
2453 * back the way you want it.
2455 public final void setMovementMethod(MovementMethod movement) {
2456 if (mMovement != movement) {
2457 mMovement = movement;
2459 if (movement != null && !(mText instanceof Spannable)) {
2463 fixFocusableAndClickableSettings();
2465 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
2467 if (mEditor != null) mEditor.prepareCursorControllers();
2471 private void fixFocusableAndClickableSettings() {
2472 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
2473 setFocusable(FOCUSABLE);
2475 setLongClickable(true);
2477 setFocusable(FOCUSABLE_AUTO);
2478 setClickable(false);
2479 setLongClickable(false);
2484 * Gets the current {@link android.text.method.TransformationMethod} for the TextView.
2485 * This is frequently null, except for single-line and password fields.
2486 * @return the current transformation method for this TextView.
2488 * @attr ref android.R.styleable#TextView_password
2489 * @attr ref android.R.styleable#TextView_singleLine
2491 public final TransformationMethod getTransformationMethod() {
2492 return mTransformation;
2496 * Sets the transformation that is applied to the text that this
2497 * TextView is displaying.
2499 * @attr ref android.R.styleable#TextView_password
2500 * @attr ref android.R.styleable#TextView_singleLine
2502 public final void setTransformationMethod(TransformationMethod method) {
2503 if (method == mTransformation) {
2504 // Avoid the setText() below if the transformation is
2508 if (mTransformation != null) {
2509 if (mText instanceof Spannable) {
2510 ((Spannable) mText).removeSpan(mTransformation);
2514 mTransformation = method;
2516 if (method instanceof TransformationMethod2) {
2517 TransformationMethod2 method2 = (TransformationMethod2) method;
2518 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
2519 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
2521 mAllowTransformationLengthChange = false;
2526 if (hasPasswordTransformationMethod()) {
2527 notifyViewAccessibilityStateChangedIfNeeded(
2528 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
2531 // PasswordTransformationMethod always have LTR text direction heuristics returned by
2532 // getTextDirectionHeuristic, needs reset
2533 mTextDir = getTextDirectionHeuristic();
2537 * Returns the top padding of the view, plus space for the top
2540 public int getCompoundPaddingTop() {
2541 final Drawables dr = mDrawables;
2542 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
2545 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
2550 * Returns the bottom padding of the view, plus space for the bottom
2553 public int getCompoundPaddingBottom() {
2554 final Drawables dr = mDrawables;
2555 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
2556 return mPaddingBottom;
2558 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
2563 * Returns the left padding of the view, plus space for the left
2566 public int getCompoundPaddingLeft() {
2567 final Drawables dr = mDrawables;
2568 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
2569 return mPaddingLeft;
2571 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
2576 * Returns the right padding of the view, plus space for the right
2579 public int getCompoundPaddingRight() {
2580 final Drawables dr = mDrawables;
2581 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
2582 return mPaddingRight;
2584 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2589 * Returns the start padding of the view, plus space for the start
2592 public int getCompoundPaddingStart() {
2594 switch(getLayoutDirection()) {
2596 case LAYOUT_DIRECTION_LTR:
2597 return getCompoundPaddingLeft();
2598 case LAYOUT_DIRECTION_RTL:
2599 return getCompoundPaddingRight();
2604 * Returns the end padding of the view, plus space for the end
2607 public int getCompoundPaddingEnd() {
2609 switch(getLayoutDirection()) {
2611 case LAYOUT_DIRECTION_LTR:
2612 return getCompoundPaddingRight();
2613 case LAYOUT_DIRECTION_RTL:
2614 return getCompoundPaddingLeft();
2619 * Returns the extended top padding of the view, including both the
2620 * top Drawable if any and any extra space to keep more than maxLines
2621 * of text from showing. It is only valid to call this after measuring.
2623 public int getExtendedPaddingTop() {
2624 if (mMaxMode != LINES) {
2625 return getCompoundPaddingTop();
2628 if (mLayout == null) {
2632 if (mLayout.getLineCount() <= mMaximum) {
2633 return getCompoundPaddingTop();
2636 int top = getCompoundPaddingTop();
2637 int bottom = getCompoundPaddingBottom();
2638 int viewht = getHeight() - top - bottom;
2639 int layoutht = mLayout.getLineTop(mMaximum);
2641 if (layoutht >= viewht) {
2645 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2646 if (gravity == Gravity.TOP) {
2648 } else if (gravity == Gravity.BOTTOM) {
2649 return top + viewht - layoutht;
2650 } else { // (gravity == Gravity.CENTER_VERTICAL)
2651 return top + (viewht - layoutht) / 2;
2656 * Returns the extended bottom padding of the view, including both the
2657 * bottom Drawable if any and any extra space to keep more than maxLines
2658 * of text from showing. It is only valid to call this after measuring.
2660 public int getExtendedPaddingBottom() {
2661 if (mMaxMode != LINES) {
2662 return getCompoundPaddingBottom();
2665 if (mLayout == null) {
2669 if (mLayout.getLineCount() <= mMaximum) {
2670 return getCompoundPaddingBottom();
2673 int top = getCompoundPaddingTop();
2674 int bottom = getCompoundPaddingBottom();
2675 int viewht = getHeight() - top - bottom;
2676 int layoutht = mLayout.getLineTop(mMaximum);
2678 if (layoutht >= viewht) {
2682 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2683 if (gravity == Gravity.TOP) {
2684 return bottom + viewht - layoutht;
2685 } else if (gravity == Gravity.BOTTOM) {
2687 } else { // (gravity == Gravity.CENTER_VERTICAL)
2688 return bottom + (viewht - layoutht) / 2;
2693 * Returns the total left padding of the view, including the left
2696 public int getTotalPaddingLeft() {
2697 return getCompoundPaddingLeft();
2701 * Returns the total right padding of the view, including the right
2704 public int getTotalPaddingRight() {
2705 return getCompoundPaddingRight();
2709 * Returns the total start padding of the view, including the start
2712 public int getTotalPaddingStart() {
2713 return getCompoundPaddingStart();
2717 * Returns the total end padding of the view, including the end
2720 public int getTotalPaddingEnd() {
2721 return getCompoundPaddingEnd();
2725 * Returns the total top padding of the view, including the top
2726 * Drawable if any, the extra space to keep more than maxLines
2727 * from showing, and the vertical offset for gravity, if any.
2729 public int getTotalPaddingTop() {
2730 return getExtendedPaddingTop() + getVerticalOffset(true);
2734 * Returns the total bottom padding of the view, including the bottom
2735 * Drawable if any, the extra space to keep more than maxLines
2736 * from showing, and the vertical offset for gravity, if any.
2738 public int getTotalPaddingBottom() {
2739 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2743 * Sets the Drawables (if any) to appear to the left of, above, to the
2744 * right of, and below the text. Use {@code null} if you do not want a
2745 * Drawable there. The Drawables must already have had
2746 * {@link Drawable#setBounds} called.
2748 * Calling this method will overwrite any Drawables previously set using
2749 * {@link #setCompoundDrawablesRelative} or related methods.
2751 * @attr ref android.R.styleable#TextView_drawableLeft
2752 * @attr ref android.R.styleable#TextView_drawableTop
2753 * @attr ref android.R.styleable#TextView_drawableRight
2754 * @attr ref android.R.styleable#TextView_drawableBottom
2756 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2757 @Nullable Drawable right, @Nullable Drawable bottom) {
2758 Drawables dr = mDrawables;
2760 // We're switching to absolute, discard relative.
2762 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2763 dr.mDrawableStart = null;
2764 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2765 dr.mDrawableEnd = null;
2766 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2767 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2770 final boolean drawables = left != null || top != null || right != null || bottom != null;
2772 // Clearing drawables... can we free the data structure?
2774 if (!dr.hasMetadata()) {
2777 // We need to retain the last set padding, so just clear
2778 // out all of the fields in the existing structure.
2779 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2780 if (dr.mShowing[i] != null) {
2781 dr.mShowing[i].setCallback(null);
2783 dr.mShowing[i] = null;
2785 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2786 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2787 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2788 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2793 mDrawables = dr = new Drawables(getContext());
2796 mDrawables.mOverride = false;
2798 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2799 dr.mShowing[Drawables.LEFT].setCallback(null);
2801 dr.mShowing[Drawables.LEFT] = left;
2803 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2804 dr.mShowing[Drawables.TOP].setCallback(null);
2806 dr.mShowing[Drawables.TOP] = top;
2808 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2809 dr.mShowing[Drawables.RIGHT].setCallback(null);
2811 dr.mShowing[Drawables.RIGHT] = right;
2813 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2814 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2816 dr.mShowing[Drawables.BOTTOM] = bottom;
2818 final Rect compoundRect = dr.mCompoundRect;
2821 state = getDrawableState();
2824 left.setState(state);
2825 left.copyBounds(compoundRect);
2826 left.setCallback(this);
2827 dr.mDrawableSizeLeft = compoundRect.width();
2828 dr.mDrawableHeightLeft = compoundRect.height();
2830 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2833 if (right != null) {
2834 right.setState(state);
2835 right.copyBounds(compoundRect);
2836 right.setCallback(this);
2837 dr.mDrawableSizeRight = compoundRect.width();
2838 dr.mDrawableHeightRight = compoundRect.height();
2840 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2844 top.setState(state);
2845 top.copyBounds(compoundRect);
2846 top.setCallback(this);
2847 dr.mDrawableSizeTop = compoundRect.height();
2848 dr.mDrawableWidthTop = compoundRect.width();
2850 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2853 if (bottom != null) {
2854 bottom.setState(state);
2855 bottom.copyBounds(compoundRect);
2856 bottom.setCallback(this);
2857 dr.mDrawableSizeBottom = compoundRect.height();
2858 dr.mDrawableWidthBottom = compoundRect.width();
2860 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2864 // Save initial left/right drawables
2866 dr.mDrawableLeftInitial = left;
2867 dr.mDrawableRightInitial = right;
2870 resetResolvedDrawables();
2872 applyCompoundDrawableTint();
2878 * Sets the Drawables (if any) to appear to the left of, above, to the
2879 * right of, and below the text. Use 0 if you do not want a Drawable there.
2880 * The Drawables' bounds will be set to their intrinsic bounds.
2882 * Calling this method will overwrite any Drawables previously set using
2883 * {@link #setCompoundDrawablesRelative} or related methods.
2885 * @param left Resource identifier of the left Drawable.
2886 * @param top Resource identifier of the top Drawable.
2887 * @param right Resource identifier of the right Drawable.
2888 * @param bottom Resource identifier of the bottom Drawable.
2890 * @attr ref android.R.styleable#TextView_drawableLeft
2891 * @attr ref android.R.styleable#TextView_drawableTop
2892 * @attr ref android.R.styleable#TextView_drawableRight
2893 * @attr ref android.R.styleable#TextView_drawableBottom
2895 @android.view.RemotableViewMethod
2896 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2897 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2898 final Context context = getContext();
2899 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2900 top != 0 ? context.getDrawable(top) : null,
2901 right != 0 ? context.getDrawable(right) : null,
2902 bottom != 0 ? context.getDrawable(bottom) : null);
2906 * Sets the Drawables (if any) to appear to the left of, above, to the
2907 * right of, and below the text. Use {@code null} if you do not want a
2908 * Drawable there. The Drawables' bounds will be set to their intrinsic
2911 * Calling this method will overwrite any Drawables previously set using
2912 * {@link #setCompoundDrawablesRelative} or related methods.
2914 * @attr ref android.R.styleable#TextView_drawableLeft
2915 * @attr ref android.R.styleable#TextView_drawableTop
2916 * @attr ref android.R.styleable#TextView_drawableRight
2917 * @attr ref android.R.styleable#TextView_drawableBottom
2919 @android.view.RemotableViewMethod
2920 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2921 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2924 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2926 if (right != null) {
2927 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2930 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2932 if (bottom != null) {
2933 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2935 setCompoundDrawables(left, top, right, bottom);
2939 * Sets the Drawables (if any) to appear to the start of, above, to the end
2940 * of, and below the text. Use {@code null} if you do not want a Drawable
2941 * there. The Drawables must already have had {@link Drawable#setBounds}
2944 * Calling this method will overwrite any Drawables previously set using
2945 * {@link #setCompoundDrawables} or related methods.
2947 * @attr ref android.R.styleable#TextView_drawableStart
2948 * @attr ref android.R.styleable#TextView_drawableTop
2949 * @attr ref android.R.styleable#TextView_drawableEnd
2950 * @attr ref android.R.styleable#TextView_drawableBottom
2952 @android.view.RemotableViewMethod
2953 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2954 @Nullable Drawable end, @Nullable Drawable bottom) {
2955 Drawables dr = mDrawables;
2957 // We're switching to relative, discard absolute.
2959 if (dr.mShowing[Drawables.LEFT] != null) {
2960 dr.mShowing[Drawables.LEFT].setCallback(null);
2962 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2963 if (dr.mShowing[Drawables.RIGHT] != null) {
2964 dr.mShowing[Drawables.RIGHT].setCallback(null);
2966 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2967 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2968 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2971 final boolean drawables = start != null || top != null
2972 || end != null || bottom != null;
2975 // Clearing drawables... can we free the data structure?
2977 if (!dr.hasMetadata()) {
2980 // We need to retain the last set padding, so just clear
2981 // out all of the fields in the existing structure.
2982 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2983 dr.mDrawableStart = null;
2984 if (dr.mShowing[Drawables.TOP] != null) {
2985 dr.mShowing[Drawables.TOP].setCallback(null);
2987 dr.mShowing[Drawables.TOP] = null;
2988 if (dr.mDrawableEnd != null) {
2989 dr.mDrawableEnd.setCallback(null);
2991 dr.mDrawableEnd = null;
2992 if (dr.mShowing[Drawables.BOTTOM] != null) {
2993 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2995 dr.mShowing[Drawables.BOTTOM] = null;
2996 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2997 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2998 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2999 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3004 mDrawables = dr = new Drawables(getContext());
3007 mDrawables.mOverride = true;
3009 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
3010 dr.mDrawableStart.setCallback(null);
3012 dr.mDrawableStart = start;
3014 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
3015 dr.mShowing[Drawables.TOP].setCallback(null);
3017 dr.mShowing[Drawables.TOP] = top;
3019 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
3020 dr.mDrawableEnd.setCallback(null);
3022 dr.mDrawableEnd = end;
3024 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
3025 dr.mShowing[Drawables.BOTTOM].setCallback(null);
3027 dr.mShowing[Drawables.BOTTOM] = bottom;
3029 final Rect compoundRect = dr.mCompoundRect;
3032 state = getDrawableState();
3034 if (start != null) {
3035 start.setState(state);
3036 start.copyBounds(compoundRect);
3037 start.setCallback(this);
3038 dr.mDrawableSizeStart = compoundRect.width();
3039 dr.mDrawableHeightStart = compoundRect.height();
3041 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
3045 end.setState(state);
3046 end.copyBounds(compoundRect);
3047 end.setCallback(this);
3048 dr.mDrawableSizeEnd = compoundRect.width();
3049 dr.mDrawableHeightEnd = compoundRect.height();
3051 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
3055 top.setState(state);
3056 top.copyBounds(compoundRect);
3057 top.setCallback(this);
3058 dr.mDrawableSizeTop = compoundRect.height();
3059 dr.mDrawableWidthTop = compoundRect.width();
3061 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
3064 if (bottom != null) {
3065 bottom.setState(state);
3066 bottom.copyBounds(compoundRect);
3067 bottom.setCallback(this);
3068 dr.mDrawableSizeBottom = compoundRect.height();
3069 dr.mDrawableWidthBottom = compoundRect.width();
3071 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
3075 resetResolvedDrawables();
3082 * Sets the Drawables (if any) to appear to the start of, above, to the end
3083 * of, and below the text. Use 0 if you do not want a Drawable there. The
3084 * Drawables' bounds will be set to their intrinsic bounds.
3086 * Calling this method will overwrite any Drawables previously set using
3087 * {@link #setCompoundDrawables} or related methods.
3089 * @param start Resource identifier of the start Drawable.
3090 * @param top Resource identifier of the top Drawable.
3091 * @param end Resource identifier of the end Drawable.
3092 * @param bottom Resource identifier of the bottom Drawable.
3094 * @attr ref android.R.styleable#TextView_drawableStart
3095 * @attr ref android.R.styleable#TextView_drawableTop
3096 * @attr ref android.R.styleable#TextView_drawableEnd
3097 * @attr ref android.R.styleable#TextView_drawableBottom
3099 @android.view.RemotableViewMethod
3100 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
3101 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
3102 final Context context = getContext();
3103 setCompoundDrawablesRelativeWithIntrinsicBounds(
3104 start != 0 ? context.getDrawable(start) : null,
3105 top != 0 ? context.getDrawable(top) : null,
3106 end != 0 ? context.getDrawable(end) : null,
3107 bottom != 0 ? context.getDrawable(bottom) : null);
3111 * Sets the Drawables (if any) to appear to the start of, above, to the end
3112 * of, and below the text. Use {@code null} if you do not want a Drawable
3113 * there. The Drawables' bounds will be set to their intrinsic bounds.
3115 * Calling this method will overwrite any Drawables previously set using
3116 * {@link #setCompoundDrawables} or related methods.
3118 * @attr ref android.R.styleable#TextView_drawableStart
3119 * @attr ref android.R.styleable#TextView_drawableTop
3120 * @attr ref android.R.styleable#TextView_drawableEnd
3121 * @attr ref android.R.styleable#TextView_drawableBottom
3123 @android.view.RemotableViewMethod
3124 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
3125 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
3127 if (start != null) {
3128 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
3131 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
3134 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
3136 if (bottom != null) {
3137 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
3139 setCompoundDrawablesRelative(start, top, end, bottom);
3143 * Returns drawables for the left, top, right, and bottom borders.
3145 * @attr ref android.R.styleable#TextView_drawableLeft
3146 * @attr ref android.R.styleable#TextView_drawableTop
3147 * @attr ref android.R.styleable#TextView_drawableRight
3148 * @attr ref android.R.styleable#TextView_drawableBottom
3151 public Drawable[] getCompoundDrawables() {
3152 final Drawables dr = mDrawables;
3154 return dr.mShowing.clone();
3156 return new Drawable[] { null, null, null, null };
3161 * Returns drawables for the start, top, end, and bottom borders.
3163 * @attr ref android.R.styleable#TextView_drawableStart
3164 * @attr ref android.R.styleable#TextView_drawableTop
3165 * @attr ref android.R.styleable#TextView_drawableEnd
3166 * @attr ref android.R.styleable#TextView_drawableBottom
3169 public Drawable[] getCompoundDrawablesRelative() {
3170 final Drawables dr = mDrawables;
3172 return new Drawable[] {
3173 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
3174 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
3177 return new Drawable[] { null, null, null, null };
3182 * Sets the size of the padding between the compound drawables and
3185 * @attr ref android.R.styleable#TextView_drawablePadding
3187 @android.view.RemotableViewMethod
3188 public void setCompoundDrawablePadding(int pad) {
3189 Drawables dr = mDrawables;
3192 dr.mDrawablePadding = pad;
3196 mDrawables = dr = new Drawables(getContext());
3198 dr.mDrawablePadding = pad;
3206 * Returns the padding between the compound drawables and the text.
3208 * @attr ref android.R.styleable#TextView_drawablePadding
3210 public int getCompoundDrawablePadding() {
3211 final Drawables dr = mDrawables;
3212 return dr != null ? dr.mDrawablePadding : 0;
3216 * Applies a tint to the compound drawables. Does not modify the
3217 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
3219 * Subsequent calls to
3220 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
3221 * and related methods will automatically mutate the drawables and apply
3222 * the specified tint and tint mode using
3223 * {@link Drawable#setTintList(ColorStateList)}.
3225 * @param tint the tint to apply, may be {@code null} to clear tint
3227 * @attr ref android.R.styleable#TextView_drawableTint
3228 * @see #getCompoundDrawableTintList()
3229 * @see Drawable#setTintList(ColorStateList)
3231 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
3232 if (mDrawables == null) {
3233 mDrawables = new Drawables(getContext());
3235 mDrawables.mTintList = tint;
3236 mDrawables.mHasTint = true;
3238 applyCompoundDrawableTint();
3242 * @return the tint applied to the compound drawables
3243 * @attr ref android.R.styleable#TextView_drawableTint
3244 * @see #setCompoundDrawableTintList(ColorStateList)
3246 public ColorStateList getCompoundDrawableTintList() {
3247 return mDrawables != null ? mDrawables.mTintList : null;
3251 * Specifies the blending mode used to apply the tint specified by
3252 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
3253 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
3255 * @param tintMode the blending mode used to apply the tint, may be
3256 * {@code null} to clear tint
3257 * @attr ref android.R.styleable#TextView_drawableTintMode
3258 * @see #setCompoundDrawableTintList(ColorStateList)
3259 * @see Drawable#setTintMode(PorterDuff.Mode)
3261 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
3262 if (mDrawables == null) {
3263 mDrawables = new Drawables(getContext());
3265 mDrawables.mTintMode = tintMode;
3266 mDrawables.mHasTintMode = true;
3268 applyCompoundDrawableTint();
3272 * Returns the blending mode used to apply the tint to the compound
3273 * drawables, if specified.
3275 * @return the blending mode used to apply the tint to the compound
3277 * @attr ref android.R.styleable#TextView_drawableTintMode
3278 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
3280 public PorterDuff.Mode getCompoundDrawableTintMode() {
3281 return mDrawables != null ? mDrawables.mTintMode : null;
3284 private void applyCompoundDrawableTint() {
3285 if (mDrawables == null) {
3289 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
3290 final ColorStateList tintList = mDrawables.mTintList;
3291 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
3292 final boolean hasTint = mDrawables.mHasTint;
3293 final boolean hasTintMode = mDrawables.mHasTintMode;
3294 final int[] state = getDrawableState();
3296 for (Drawable dr : mDrawables.mShowing) {
3301 if (dr == mDrawables.mDrawableError) {
3302 // From a developer's perspective, the error drawable isn't
3303 // a compound drawable. Don't apply the generic compound
3304 // drawable tint to it.
3311 dr.setTintList(tintList);
3315 dr.setTintMode(tintMode);
3318 // The drawable (or one of its children) may not have been
3319 // stateful before applying the tint, so let's try again.
3320 if (dr.isStateful()) {
3328 public void setPadding(int left, int top, int right, int bottom) {
3329 if (left != mPaddingLeft
3330 || right != mPaddingRight
3331 || top != mPaddingTop
3332 || bottom != mPaddingBottom) {
3336 // the super call will requestLayout()
3337 super.setPadding(left, top, right, bottom);
3342 public void setPaddingRelative(int start, int top, int end, int bottom) {
3343 if (start != getPaddingStart()
3344 || end != getPaddingEnd()
3345 || top != mPaddingTop
3346 || bottom != mPaddingBottom) {
3350 // the super call will requestLayout()
3351 super.setPaddingRelative(start, top, end, bottom);
3356 * Gets the autolink mask of the text. See {@link
3357 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3360 * @attr ref android.R.styleable#TextView_autoLink
3362 public final int getAutoLinkMask() {
3363 return mAutoLinkMask;
3367 * Sets the text appearance from the specified style resource.
3369 * Use a framework-defined {@code TextAppearance} style like
3370 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
3371 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
3372 * set of attributes that can be used in a custom style.
3374 * @param resId the resource identifier of the style to apply
3375 * @attr ref android.R.styleable#TextView_textAppearance
3377 @SuppressWarnings("deprecation")
3378 public void setTextAppearance(@StyleRes int resId) {
3379 setTextAppearance(mContext, resId);
3383 * Sets the text color, size, style, hint color, and highlight color
3384 * from the specified TextAppearance resource.
3386 * @deprecated Use {@link #setTextAppearance(int)} instead.
3389 public void setTextAppearance(Context context, @StyleRes int resId) {
3390 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
3392 final int textColorHighlight = ta.getColor(
3393 R.styleable.TextAppearance_textColorHighlight, 0);
3394 if (textColorHighlight != 0) {
3395 setHighlightColor(textColorHighlight);
3398 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
3399 if (textColor != null) {
3400 setTextColor(textColor);
3403 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
3404 if (textSize != 0) {
3405 setRawTextSize(textSize, true /* shouldRequestLayout */);
3408 final ColorStateList textColorHint = ta.getColorStateList(
3409 R.styleable.TextAppearance_textColorHint);
3410 if (textColorHint != null) {
3411 setHintTextColor(textColorHint);
3414 final ColorStateList textColorLink = ta.getColorStateList(
3415 R.styleable.TextAppearance_textColorLink);
3416 if (textColorLink != null) {
3417 setLinkTextColor(textColorLink);
3420 Typeface fontTypeface = null;
3421 String fontFamily = null;
3422 if (!context.isRestricted() && context.canLoadUnsafeResources()) {
3424 fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
3425 } catch (UnsupportedOperationException | Resources.NotFoundException e) {
3426 // Expected if it is not a font resource.
3429 if (fontTypeface == null) {
3430 fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
3432 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
3433 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
3434 setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
3436 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
3437 if (shadowColor != 0) {
3438 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
3439 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
3440 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
3441 setShadowLayer(r, dx, dy, shadowColor);
3444 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
3445 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
3448 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
3449 setElegantTextHeight(ta.getBoolean(
3450 R.styleable.TextAppearance_elegantTextHeight, false));
3453 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
3454 setLetterSpacing(ta.getFloat(
3455 R.styleable.TextAppearance_letterSpacing, 0));
3458 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
3459 setFontFeatureSettings(ta.getString(
3460 R.styleable.TextAppearance_fontFeatureSettings));
3467 * Get the default primary {@link Locale} of the text in this TextView. This will always be
3468 * the first member of {@link #getTextLocales()}.
3469 * @return the default primary {@link Locale} of the text in this TextView.
3472 public Locale getTextLocale() {
3473 return mTextPaint.getTextLocale();
3477 * Get the default {@link LocaleList} of the text in this TextView.
3478 * @return the default {@link LocaleList} of the text in this TextView.
3480 @NonNull @Size(min = 1)
3481 public LocaleList getTextLocales() {
3482 return mTextPaint.getTextLocales();
3485 private void changeListenerLocaleTo(@Nullable Locale locale) {
3486 if (mListenerChanged) {
3487 // If a listener has been explicitly set, don't change it. We may break something.
3490 // The following null check is not absolutely necessary since all calling points of
3491 // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left
3492 // here in case others would want to call this method in the future.
3493 if (mEditor != null) {
3494 KeyListener listener = mEditor.mKeyListener;
3495 if (listener instanceof DigitsKeyListener) {
3496 listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
3497 } else if (listener instanceof DateKeyListener) {
3498 listener = DateKeyListener.getInstance(locale);
3499 } else if (listener instanceof TimeKeyListener) {
3500 listener = TimeKeyListener.getInstance(locale);
3501 } else if (listener instanceof DateTimeKeyListener) {
3502 listener = DateTimeKeyListener.getInstance(locale);
3506 final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType);
3507 setKeyListenerOnly(listener);
3508 setInputTypeFromEditor();
3509 if (wasPasswordType) {
3510 final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS;
3511 if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) {
3512 mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
3513 } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) {
3514 mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
3521 * Set the default {@link Locale} of the text in this TextView to a one-member
3522 * {@link LocaleList} containing just the given Locale.
3524 * @param locale the {@link Locale} for drawing text, must not be null.
3526 * @see #setTextLocales
3528 public void setTextLocale(@NonNull Locale locale) {
3529 mLocalesChanged = true;
3530 mTextPaint.setTextLocale(locale);
3531 if (mLayout != null) {
3539 * Set the default {@link LocaleList} of the text in this TextView to the given value.
3541 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
3542 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
3543 * other aspects of text display, including line breaking.
3545 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
3547 * @see Paint#setTextLocales
3549 public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
3550 mLocalesChanged = true;
3551 mTextPaint.setTextLocales(locales);
3552 if (mLayout != null) {
3560 protected void onConfigurationChanged(Configuration newConfig) {
3561 super.onConfigurationChanged(newConfig);
3562 if (!mLocalesChanged) {
3563 mTextPaint.setTextLocales(LocaleList.getDefault());
3564 if (mLayout != null) {
3573 * @return the size (in pixels) of the default text size in this TextView.
3575 @ViewDebug.ExportedProperty(category = "text")
3576 public float getTextSize() {
3577 return mTextPaint.getTextSize();
3581 * @return the size (in scaled pixels) of the default text size in this TextView.
3584 @ViewDebug.ExportedProperty(category = "text")
3585 public float getScaledTextSize() {
3586 return mTextPaint.getTextSize() / mTextPaint.density;
3590 @ViewDebug.ExportedProperty(category = "text", mapping = {
3591 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
3592 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
3593 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
3594 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
3596 public int getTypefaceStyle() {
3597 Typeface typeface = mTextPaint.getTypeface();
3598 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
3602 * Set the default text size to the given value, interpreted as "scaled
3603 * pixel" units. This size is adjusted based on the current density and
3604 * user font size preference.
3606 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3608 * @param size The scaled pixel size.
3610 * @attr ref android.R.styleable#TextView_textSize
3612 @android.view.RemotableViewMethod
3613 public void setTextSize(float size) {
3614 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
3618 * Set the default text size to a given unit and value. See {@link
3619 * TypedValue} for the possible dimension units.
3621 * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op.
3623 * @param unit The desired dimension unit.
3624 * @param size The desired size in the given units.
3626 * @attr ref android.R.styleable#TextView_textSize
3628 public void setTextSize(int unit, float size) {
3629 if (!isAutoSizeEnabled()) {
3630 setTextSizeInternal(unit, size, true /* shouldRequestLayout */);
3634 private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
3635 Context c = getContext();
3639 r = Resources.getSystem();
3641 r = c.getResources();
3644 setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
3645 shouldRequestLayout);
3648 private void setRawTextSize(float size, boolean shouldRequestLayout) {
3649 if (size != mTextPaint.getTextSize()) {
3650 mTextPaint.setTextSize(size);
3652 if (shouldRequestLayout && mLayout != null) {
3653 // Do not auto-size right after setting the text size.
3654 mNeedsAutoSizeText = false;
3663 * Gets the extent by which text should be stretched horizontally.
3664 * This will usually be 1.0.
3665 * @return The horizontal scale factor.
3667 public float getTextScaleX() {
3668 return mTextPaint.getTextScaleX();
3672 * Sets the horizontal scale factor for text. The default value
3673 * is 1.0. Values greater than 1.0 stretch the text wider.
3674 * Values less than 1.0 make the text narrower. By default, this value is 1.0.
3675 * @param size The horizontal scale factor.
3676 * @attr ref android.R.styleable#TextView_textScaleX
3678 @android.view.RemotableViewMethod
3679 public void setTextScaleX(float size) {
3680 if (size != mTextPaint.getTextScaleX()) {
3681 mUserSetTextScaleX = true;
3682 mTextPaint.setTextScaleX(size);
3684 if (mLayout != null) {
3693 * Sets the typeface and style in which the text should be displayed.
3694 * Note that not all Typeface families actually have bold and italic
3695 * variants, so you may need to use
3696 * {@link #setTypeface(Typeface, int)} to get the appearance
3697 * that you actually want.
3699 * @see #getTypeface()
3701 * @attr ref android.R.styleable#TextView_fontFamily
3702 * @attr ref android.R.styleable#TextView_typeface
3703 * @attr ref android.R.styleable#TextView_textStyle
3705 public void setTypeface(Typeface tf) {
3706 if (mTextPaint.getTypeface() != tf) {
3707 mTextPaint.setTypeface(tf);
3709 if (mLayout != null) {
3718 * Gets the current {@link Typeface} that is used to style the text.
3719 * @return The current Typeface.
3721 * @see #setTypeface(Typeface)
3723 * @attr ref android.R.styleable#TextView_fontFamily
3724 * @attr ref android.R.styleable#TextView_typeface
3725 * @attr ref android.R.styleable#TextView_textStyle
3727 public Typeface getTypeface() {
3728 return mTextPaint.getTypeface();
3732 * Set the TextView's elegant height metrics flag. This setting selects font
3733 * variants that have not been compacted to fit Latin-based vertical
3734 * metrics, and also increases top and bottom bounds to provide more space.
3736 * @param elegant set the paint's elegant metrics flag.
3738 * @attr ref android.R.styleable#TextView_elegantTextHeight
3740 public void setElegantTextHeight(boolean elegant) {
3741 if (elegant != mTextPaint.isElegantTextHeight()) {
3742 mTextPaint.setElegantTextHeight(elegant);
3743 if (mLayout != null) {
3752 * Gets the text letter-space value, which determines the spacing between characters.
3753 * The value returned is in ems. Normally, this value is 0.0.
3754 * @return The text letter-space value in ems.
3756 * @see #setLetterSpacing(float)
3757 * @see Paint#setLetterSpacing
3759 public float getLetterSpacing() {
3760 return mTextPaint.getLetterSpacing();
3764 * Sets text letter-spacing in em units. Typical values
3765 * for slight expansion will be around 0.05. Negative values tighten text.
3767 * @see #getLetterSpacing()
3768 * @see Paint#getLetterSpacing
3770 * @param letterSpacing A text letter-space value in ems.
3771 * @attr ref android.R.styleable#TextView_letterSpacing
3773 @android.view.RemotableViewMethod
3774 public void setLetterSpacing(float letterSpacing) {
3775 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3776 mTextPaint.setLetterSpacing(letterSpacing);
3778 if (mLayout != null) {
3787 * Returns the font feature settings. The format is the same as the CSS
3788 * font-feature-settings attribute:
3789 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3790 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3792 * @return the currently set font feature settings. Default is null.
3794 * @see #setFontFeatureSettings(String)
3795 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
3798 public String getFontFeatureSettings() {
3799 return mTextPaint.getFontFeatureSettings();
3803 * Returns the font variation settings.
3805 * @return the currently set font variation settings. Returns null if no variation is
3808 * @see #setFontVariationSettings(String)
3809 * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String)
3812 public String getFontVariationSettings() {
3813 return mTextPaint.getFontVariationSettings();
3817 * Sets the break strategy for breaking paragraphs into lines. The default value for
3818 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3819 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3820 * text "dancing" when being edited.
3822 * @attr ref android.R.styleable#TextView_breakStrategy
3823 * @see #getBreakStrategy()
3825 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3826 mBreakStrategy = breakStrategy;
3827 if (mLayout != null) {
3835 * Gets the current strategy for breaking paragraphs into lines.
3836 * @return the current strategy for breaking paragraphs into lines.
3838 * @attr ref android.R.styleable#TextView_breakStrategy
3839 * @see #setBreakStrategy(int)
3841 @Layout.BreakStrategy
3842 public int getBreakStrategy() {
3843 return mBreakStrategy;
3847 * Sets the frequency of automatic hyphenation to use when determining word breaks.
3848 * The default value for both TextView and {@link EditText} is
3849 * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3850 * Note that the default hyphenation frequency value is set from the theme.
3852 * @param hyphenationFrequency The hyphenation frequency to use.
3853 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3854 * @see #getHyphenationFrequency()
3856 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3857 mHyphenationFrequency = hyphenationFrequency;
3858 if (mLayout != null) {
3866 * Gets the current frequency of automatic hyphenation to be used when determining word breaks.
3867 * @return the current frequency of automatic hyphenation to be used when determining word
3870 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3871 * @see #setHyphenationFrequency(int)
3873 @Layout.HyphenationFrequency
3874 public int getHyphenationFrequency() {
3875 return mHyphenationFrequency;
3879 * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the
3880 * last line is too short for justification, the last line will be displayed with the
3881 * alignment set by {@link android.view.View#setTextAlignment}.
3883 * @see #getJustificationMode()
3885 @Layout.JustificationMode
3886 public void setJustificationMode(@Layout.JustificationMode int justificationMode) {
3887 mJustificationMode = justificationMode;
3888 if (mLayout != null) {
3896 * @return true if currently paragraph justification mode.
3898 * @see #setJustificationMode(int)
3900 public @Layout.JustificationMode int getJustificationMode() {
3901 return mJustificationMode;
3905 * Sets font feature settings. The format is the same as the CSS
3906 * font-feature-settings attribute:
3907 * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop">
3908 * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a>
3910 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3912 * @see #getFontFeatureSettings()
3913 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
3915 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3917 @android.view.RemotableViewMethod
3918 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3919 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3920 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3922 if (mLayout != null) {
3932 * Sets TrueType or OpenType font variation settings. The settings string is constructed from
3933 * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters
3934 * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that
3935 * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E
3936 * are invalid. If a specified axis name is not defined in the font, the settings will be
3942 * <li>Set font width to 150.
3945 * TextView textView = (TextView) findViewById(R.id.textView);
3946 * textView.setFontVariationSettings("'wdth' 150");
3951 * <li>Set the font slant to 20 degrees and ask for italic style.
3954 * TextView textView = (TextView) findViewById(R.id.textView);
3955 * textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
3962 * @param fontVariationSettings font variation settings. You can pass null or empty string as
3963 * no variation settings.
3964 * @return true if the given settings is effective to at least one font file underlying this
3965 * TextView. This function also returns true for empty settings string. Otherwise
3968 * @throws IllegalArgumentException If given string is not a valid font variation settings
3971 * @see #getFontVariationSettings()
3972 * @see FontVariationAxis
3974 public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
3975 final String existingSettings = mTextPaint.getFontVariationSettings();
3976 if (fontVariationSettings == existingSettings
3977 || (fontVariationSettings != null
3978 && fontVariationSettings.equals(existingSettings))) {
3981 boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
3983 if (effective && mLayout != null) {
3992 * Sets the text color for all the states (normal, selected,
3993 * focused) to be this color.
3995 * @param color A color value in the form 0xAARRGGBB.
3996 * Do not pass a resource ID. To get a color value from a resource ID, call
3997 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
3999 * @see #setTextColor(ColorStateList)
4000 * @see #getTextColors()
4002 * @attr ref android.R.styleable#TextView_textColor
4004 @android.view.RemotableViewMethod
4005 public void setTextColor(@ColorInt int color) {
4006 mTextColor = ColorStateList.valueOf(color);
4011 * Sets the text color.
4013 * @see #setTextColor(int)
4014 * @see #getTextColors()
4015 * @see #setHintTextColor(ColorStateList)
4016 * @see #setLinkTextColor(ColorStateList)
4018 * @attr ref android.R.styleable#TextView_textColor
4020 @android.view.RemotableViewMethod
4021 public void setTextColor(ColorStateList colors) {
4022 if (colors == null) {
4023 throw new NullPointerException();
4026 mTextColor = colors;
4031 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
4033 * @see #setTextColor(ColorStateList)
4034 * @see #setTextColor(int)
4036 * @attr ref android.R.styleable#TextView_textColor
4038 public final ColorStateList getTextColors() {
4043 * Return the current color selected for normal text.
4045 * @return Returns the current text color.
4048 public final int getCurrentTextColor() {
4049 return mCurTextColor;
4053 * Sets the color used to display the selection highlight.
4055 * @attr ref android.R.styleable#TextView_textColorHighlight
4057 @android.view.RemotableViewMethod
4058 public void setHighlightColor(@ColorInt int color) {
4059 if (mHighlightColor != color) {
4060 mHighlightColor = color;
4066 * @return the color used to display the selection highlight
4068 * @see #setHighlightColor(int)
4070 * @attr ref android.R.styleable#TextView_textColorHighlight
4073 public int getHighlightColor() {
4074 return mHighlightColor;
4078 * Sets whether the soft input method will be made visible when this
4079 * TextView gets focused. The default is true.
4081 @android.view.RemotableViewMethod
4082 public final void setShowSoftInputOnFocus(boolean show) {
4083 createEditorIfNeeded();
4084 mEditor.mShowSoftInputOnFocus = show;
4088 * Returns whether the soft input method will be made visible when this
4089 * TextView gets focused. The default is true.
4091 public final boolean getShowSoftInputOnFocus() {
4092 // When there is no Editor, return default true value
4093 return mEditor == null || mEditor.mShowSoftInputOnFocus;
4097 * Gives the text a shadow of the specified blur radius and color, the specified
4098 * distance from its drawn position.
4100 * The text shadow produced does not interact with the properties on view
4101 * that are responsible for real time shadows,
4102 * {@link View#getElevation() elevation} and
4103 * {@link View#getTranslationZ() translationZ}.
4105 * @see Paint#setShadowLayer(float, float, float, int)
4107 * @attr ref android.R.styleable#TextView_shadowColor
4108 * @attr ref android.R.styleable#TextView_shadowDx
4109 * @attr ref android.R.styleable#TextView_shadowDy
4110 * @attr ref android.R.styleable#TextView_shadowRadius
4112 public void setShadowLayer(float radius, float dx, float dy, int color) {
4113 mTextPaint.setShadowLayer(radius, dx, dy, color);
4115 mShadowRadius = radius;
4118 mShadowColor = color;
4120 // Will change text clip region
4121 if (mEditor != null) {
4122 mEditor.invalidateTextDisplayList();
4123 mEditor.invalidateHandlesAndActionMode();
4129 * Gets the radius of the shadow layer.
4131 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
4133 * @see #setShadowLayer(float, float, float, int)
4135 * @attr ref android.R.styleable#TextView_shadowRadius
4137 public float getShadowRadius() {
4138 return mShadowRadius;
4142 * @return the horizontal offset of the shadow layer
4144 * @see #setShadowLayer(float, float, float, int)
4146 * @attr ref android.R.styleable#TextView_shadowDx
4148 public float getShadowDx() {
4153 * Gets the vertical offset of the shadow layer.
4154 * @return The vertical offset of the shadow layer.
4156 * @see #setShadowLayer(float, float, float, int)
4158 * @attr ref android.R.styleable#TextView_shadowDy
4160 public float getShadowDy() {
4165 * Gets the color of the shadow layer.
4166 * @return the color of the shadow layer
4168 * @see #setShadowLayer(float, float, float, int)
4170 * @attr ref android.R.styleable#TextView_shadowColor
4173 public int getShadowColor() {
4174 return mShadowColor;
4178 * Gets the {@link TextPaint} used for the text.
4179 * Use this only to consult the Paint's properties and not to change them.
4180 * @return The base paint used for the text.
4182 public TextPaint getPaint() {
4187 * Sets the autolink mask of the text. See {@link
4188 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
4191 * @attr ref android.R.styleable#TextView_autoLink
4193 @android.view.RemotableViewMethod
4194 public final void setAutoLinkMask(int mask) {
4195 mAutoLinkMask = mask;
4199 * Sets whether the movement method will automatically be set to
4200 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4201 * set to nonzero and links are detected in {@link #setText}.
4202 * The default is true.
4204 * @attr ref android.R.styleable#TextView_linksClickable
4206 @android.view.RemotableViewMethod
4207 public final void setLinksClickable(boolean whether) {
4208 mLinksClickable = whether;
4212 * Returns whether the movement method will automatically be set to
4213 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
4214 * set to nonzero and links are detected in {@link #setText}.
4215 * The default is true.
4217 * @attr ref android.R.styleable#TextView_linksClickable
4219 public final boolean getLinksClickable() {
4220 return mLinksClickable;
4224 * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text
4225 * (by {@link Linkify} or otherwise) if any. You can call
4226 * {@link URLSpan#getURL} on them to find where they link to
4227 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
4228 * to find the region of the text they are attached to.
4230 public URLSpan[] getUrls() {
4231 if (mText instanceof Spanned) {
4232 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
4234 return new URLSpan[0];
4239 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
4242 * @see #setHintTextColor(ColorStateList)
4243 * @see #getHintTextColors()
4244 * @see #setTextColor(int)
4246 * @attr ref android.R.styleable#TextView_textColorHint
4248 @android.view.RemotableViewMethod
4249 public final void setHintTextColor(@ColorInt int color) {
4250 mHintTextColor = ColorStateList.valueOf(color);
4255 * Sets the color of the hint text.
4257 * @see #getHintTextColors()
4258 * @see #setHintTextColor(int)
4259 * @see #setTextColor(ColorStateList)
4260 * @see #setLinkTextColor(ColorStateList)
4262 * @attr ref android.R.styleable#TextView_textColorHint
4264 public final void setHintTextColor(ColorStateList colors) {
4265 mHintTextColor = colors;
4270 * @return the color of the hint text, for the different states of this TextView.
4272 * @see #setHintTextColor(ColorStateList)
4273 * @see #setHintTextColor(int)
4274 * @see #setTextColor(ColorStateList)
4275 * @see #setLinkTextColor(ColorStateList)
4277 * @attr ref android.R.styleable#TextView_textColorHint
4279 public final ColorStateList getHintTextColors() {
4280 return mHintTextColor;
4284 * <p>Return the current color selected to paint the hint text.</p>
4286 * @return Returns the current hint text color.
4289 public final int getCurrentHintTextColor() {
4290 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
4294 * Sets the color of links in the text.
4296 * @see #setLinkTextColor(ColorStateList)
4297 * @see #getLinkTextColors()
4299 * @attr ref android.R.styleable#TextView_textColorLink
4301 @android.view.RemotableViewMethod
4302 public final void setLinkTextColor(@ColorInt int color) {
4303 mLinkTextColor = ColorStateList.valueOf(color);
4308 * Sets the color of links in the text.
4310 * @see #setLinkTextColor(int)
4311 * @see #getLinkTextColors()
4312 * @see #setTextColor(ColorStateList)
4313 * @see #setHintTextColor(ColorStateList)
4315 * @attr ref android.R.styleable#TextView_textColorLink
4317 public final void setLinkTextColor(ColorStateList colors) {
4318 mLinkTextColor = colors;
4323 * @return the list of colors used to paint the links in the text, for the different states of
4326 * @see #setLinkTextColor(ColorStateList)
4327 * @see #setLinkTextColor(int)
4329 * @attr ref android.R.styleable#TextView_textColorLink
4331 public final ColorStateList getLinkTextColors() {
4332 return mLinkTextColor;
4336 * Sets the horizontal alignment of the text and the
4337 * vertical gravity that will be used when there is extra space
4338 * in the TextView beyond what is required for the text itself.
4340 * @see android.view.Gravity
4341 * @attr ref android.R.styleable#TextView_gravity
4343 public void setGravity(int gravity) {
4344 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
4345 gravity |= Gravity.START;
4347 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
4348 gravity |= Gravity.TOP;
4351 boolean newLayout = false;
4353 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)
4354 != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
4358 if (gravity != mGravity) {
4364 if (mLayout != null && newLayout) {
4365 // XXX this is heavy-handed because no actual content changes.
4366 int want = mLayout.getWidth();
4367 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
4369 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
4370 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true);
4375 * Returns the horizontal and vertical alignment of this TextView.
4377 * @see android.view.Gravity
4378 * @attr ref android.R.styleable#TextView_gravity
4380 public int getGravity() {
4385 * Gets the flags on the Paint being used to display the text.
4386 * @return The flags on the Paint being used to display the text.
4387 * @see Paint#getFlags
4389 public int getPaintFlags() {
4390 return mTextPaint.getFlags();
4394 * Sets flags on the Paint being used to display the text and
4395 * reflows the text if they are different from the old flags.
4396 * @see Paint#setFlags
4398 @android.view.RemotableViewMethod
4399 public void setPaintFlags(int flags) {
4400 if (mTextPaint.getFlags() != flags) {
4401 mTextPaint.setFlags(flags);
4403 if (mLayout != null) {
4412 * Sets whether the text should be allowed to be wider than the
4413 * View is. If false, it will be wrapped to the width of the View.
4415 * @attr ref android.R.styleable#TextView_scrollHorizontally
4417 public void setHorizontallyScrolling(boolean whether) {
4418 if (mHorizontallyScrolling != whether) {
4419 mHorizontallyScrolling = whether;
4421 if (mLayout != null) {
4430 * Returns whether the text is allowed to be wider than the View is.
4431 * If false, the text will be wrapped to the width of the View.
4433 * @attr ref android.R.styleable#TextView_scrollHorizontally
4436 public boolean getHorizontallyScrolling() {
4437 return mHorizontallyScrolling;
4441 * Sets the height of the TextView to be at least {@code minLines} tall.
4443 * This value is used for height calculation if LayoutParams does not force TextView to have an
4444 * exact height. Setting this value overrides other previous minimum height configurations such
4445 * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set
4448 * @param minLines the minimum height of TextView in terms of number of lines
4450 * @see #getMinLines()
4451 * @see #setLines(int)
4453 * @attr ref android.R.styleable#TextView_minLines
4455 @android.view.RemotableViewMethod
4456 public void setMinLines(int minLines) {
4457 mMinimum = minLines;
4465 * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum
4466 * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}.
4468 * @return the minimum height of TextView in terms of number of lines or -1 if the minimum
4469 * height is not defined in lines
4471 * @see #setMinLines(int)
4472 * @see #setLines(int)
4474 * @attr ref android.R.styleable#TextView_minLines
4476 public int getMinLines() {
4477 return mMinMode == LINES ? mMinimum : -1;
4481 * Sets the height of the TextView to be at least {@code minPixels} tall.
4483 * This value is used for height calculation if LayoutParams does not force TextView to have an
4484 * exact height. Setting this value overrides previous minimum height configurations such as
4485 * {@link #setMinLines(int)} or {@link #setLines(int)}.
4487 * The value given here is different than {@link #setMinimumHeight(int)}. Between
4488 * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is
4489 * used to decide the final height.
4491 * @param minPixels the minimum height of TextView in terms of pixels
4493 * @see #getMinHeight()
4494 * @see #setHeight(int)
4496 * @attr ref android.R.styleable#TextView_minHeight
4498 @android.view.RemotableViewMethod
4499 public void setMinHeight(int minPixels) {
4500 mMinimum = minPixels;
4508 * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was
4509 * set using {@link #setMinLines(int)} or {@link #setLines(int)}.
4511 * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not
4514 * @see #setMinHeight(int)
4515 * @see #setHeight(int)
4517 * @attr ref android.R.styleable#TextView_minHeight
4519 public int getMinHeight() {
4520 return mMinMode == PIXELS ? mMinimum : -1;
4524 * Sets the height of the TextView to be at most {@code maxLines} tall.
4526 * This value is used for height calculation if LayoutParams does not force TextView to have an
4527 * exact height. Setting this value overrides previous maximum height configurations such as
4528 * {@link #setMaxHeight(int)} or {@link #setLines(int)}.
4530 * @param maxLines the maximum height of TextView in terms of number of lines
4532 * @see #getMaxLines()
4533 * @see #setLines(int)
4535 * @attr ref android.R.styleable#TextView_maxLines
4537 @android.view.RemotableViewMethod
4538 public void setMaxLines(int maxLines) {
4539 mMaximum = maxLines;
4547 * Returns the maximum height of TextView in terms of number of lines or -1 if the
4548 * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}.
4550 * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height
4551 * is not defined in lines.
4553 * @see #setMaxLines(int)
4554 * @see #setLines(int)
4556 * @attr ref android.R.styleable#TextView_maxLines
4558 public int getMaxLines() {
4559 return mMaxMode == LINES ? mMaximum : -1;
4563 * Sets the height of the TextView to be at most {@code maxPixels} tall.
4565 * This value is used for height calculation if LayoutParams does not force TextView to have an
4566 * exact height. Setting this value overrides previous maximum height configurations such as
4567 * {@link #setMaxLines(int)} or {@link #setLines(int)}.
4569 * @param maxPixels the maximum height of TextView in terms of pixels
4571 * @see #getMaxHeight()
4572 * @see #setHeight(int)
4574 * @attr ref android.R.styleable#TextView_maxHeight
4576 @android.view.RemotableViewMethod
4577 public void setMaxHeight(int maxPixels) {
4578 mMaximum = maxPixels;
4586 * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was
4587 * set using {@link #setMaxLines(int)} or {@link #setLines(int)}.
4589 * @return the maximum height of TextView in terms of pixels or -1 if the maximum height
4590 * is not defined in pixels
4592 * @see #setMaxHeight(int)
4593 * @see #setHeight(int)
4595 * @attr ref android.R.styleable#TextView_maxHeight
4597 public int getMaxHeight() {
4598 return mMaxMode == PIXELS ? mMaximum : -1;
4602 * Sets the height of the TextView to be exactly {@code lines} tall.
4604 * This value is used for height calculation if LayoutParams does not force TextView to have an
4605 * exact height. Setting this value overrides previous minimum/maximum height configurations
4606 * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will
4607 * set this value to 1.
4609 * @param lines the exact height of the TextView in terms of lines
4611 * @see #setHeight(int)
4613 * @attr ref android.R.styleable#TextView_lines
4615 @android.view.RemotableViewMethod
4616 public void setLines(int lines) {
4617 mMaximum = mMinimum = lines;
4618 mMaxMode = mMinMode = LINES;
4625 * Sets the height of the TextView to be exactly <code>pixels</code> tall.
4627 * This value is used for height calculation if LayoutParams does not force TextView to have an
4628 * exact height. Setting this value overrides previous minimum/maximum height configurations
4629 * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}.
4631 * @param pixels the exact height of the TextView in terms of pixels
4633 * @see #setLines(int)
4635 * @attr ref android.R.styleable#TextView_height
4637 @android.view.RemotableViewMethod
4638 public void setHeight(int pixels) {
4639 mMaximum = mMinimum = pixels;
4640 mMaxMode = mMinMode = PIXELS;
4647 * Sets the width of the TextView to be at least {@code minEms} wide.
4649 * This value is used for width calculation if LayoutParams does not force TextView to have an
4650 * exact width. Setting this value overrides previous minimum width configurations such as
4651 * {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4653 * @param minEms the minimum width of TextView in terms of ems
4658 * @attr ref android.R.styleable#TextView_minEms
4660 @android.view.RemotableViewMethod
4661 public void setMinEms(int minEms) {
4663 mMinWidthMode = EMS;
4670 * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set
4671 * using {@link #setMinWidth(int)} or {@link #setWidth(int)}.
4673 * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not
4676 * @see #setMinEms(int)
4679 * @attr ref android.R.styleable#TextView_minEms
4681 public int getMinEms() {
4682 return mMinWidthMode == EMS ? mMinWidth : -1;
4686 * Sets the width of the TextView to be at least {@code minPixels} wide.
4688 * This value is used for width calculation if LayoutParams does not force TextView to have an
4689 * exact width. Setting this value overrides previous minimum width configurations such as
4690 * {@link #setMinEms(int)} or {@link #setEms(int)}.
4692 * The value given here is different than {@link #setMinimumWidth(int)}. Between
4693 * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used
4694 * to decide the final width.
4696 * @param minPixels the minimum width of TextView in terms of pixels
4698 * @see #getMinWidth()
4699 * @see #setWidth(int)
4701 * @attr ref android.R.styleable#TextView_minWidth
4703 @android.view.RemotableViewMethod
4704 public void setMinWidth(int minPixels) {
4705 mMinWidth = minPixels;
4706 mMinWidthMode = PIXELS;
4713 * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set
4714 * using {@link #setMinEms(int)} or {@link #setEms(int)}.
4716 * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not
4719 * @see #setMinWidth(int)
4720 * @see #setWidth(int)
4722 * @attr ref android.R.styleable#TextView_minWidth
4724 public int getMinWidth() {
4725 return mMinWidthMode == PIXELS ? mMinWidth : -1;
4729 * Sets the width of the TextView to be at most {@code maxEms} wide.
4731 * This value is used for width calculation if LayoutParams does not force TextView to have an
4732 * exact width. Setting this value overrides previous maximum width configurations such as
4733 * {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4735 * @param maxEms the maximum width of TextView in terms of ems
4740 * @attr ref android.R.styleable#TextView_maxEms
4742 @android.view.RemotableViewMethod
4743 public void setMaxEms(int maxEms) {
4745 mMaxWidthMode = EMS;
4752 * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set
4753 * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}.
4755 * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not
4758 * @see #setMaxEms(int)
4761 * @attr ref android.R.styleable#TextView_maxEms
4763 public int getMaxEms() {
4764 return mMaxWidthMode == EMS ? mMaxWidth : -1;
4768 * Sets the width of the TextView to be at most {@code maxPixels} wide.
4770 * This value is used for width calculation if LayoutParams does not force TextView to have an
4771 * exact width. Setting this value overrides previous maximum width configurations such as
4772 * {@link #setMaxEms(int)} or {@link #setEms(int)}.
4774 * @param maxPixels the maximum width of TextView in terms of pixels
4776 * @see #getMaxWidth()
4777 * @see #setWidth(int)
4779 * @attr ref android.R.styleable#TextView_maxWidth
4781 @android.view.RemotableViewMethod
4782 public void setMaxWidth(int maxPixels) {
4783 mMaxWidth = maxPixels;
4784 mMaxWidthMode = PIXELS;
4791 * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set
4792 * using {@link #setMaxEms(int)} or {@link #setEms(int)}.
4794 * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not
4797 * @see #setMaxWidth(int)
4798 * @see #setWidth(int)
4800 * @attr ref android.R.styleable#TextView_maxWidth
4802 public int getMaxWidth() {
4803 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
4807 * Sets the width of the TextView to be exactly {@code ems} wide.
4809 * This value is used for width calculation if LayoutParams does not force TextView to have an
4810 * exact width. Setting this value overrides previous minimum/maximum configurations such as
4811 * {@link #setMinEms(int)} or {@link #setMaxEms(int)}.
4813 * @param ems the exact width of the TextView in terms of ems
4815 * @see #setWidth(int)
4817 * @attr ref android.R.styleable#TextView_ems
4819 @android.view.RemotableViewMethod
4820 public void setEms(int ems) {
4821 mMaxWidth = mMinWidth = ems;
4822 mMaxWidthMode = mMinWidthMode = EMS;
4829 * Sets the width of the TextView to be exactly {@code pixels} wide.
4831 * This value is used for width calculation if LayoutParams does not force TextView to have an
4832 * exact width. Setting this value overrides previous minimum/maximum width configurations
4833 * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}.
4835 * @param pixels the exact width of the TextView in terms of pixels
4839 * @attr ref android.R.styleable#TextView_width
4841 @android.view.RemotableViewMethod
4842 public void setWidth(int pixels) {
4843 mMaxWidth = mMinWidth = pixels;
4844 mMaxWidthMode = mMinWidthMode = PIXELS;
4851 * Sets line spacing for this TextView. Each line will have its height
4852 * multiplied by <code>mult</code> and have <code>add</code> added to it.
4854 * @attr ref android.R.styleable#TextView_lineSpacingExtra
4855 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4857 public void setLineSpacing(float add, float mult) {
4858 if (mSpacingAdd != add || mSpacingMult != mult) {
4860 mSpacingMult = mult;
4862 if (mLayout != null) {
4871 * Gets the line spacing multiplier
4873 * @return the value by which each line's height is multiplied to get its actual height.
4875 * @see #setLineSpacing(float, float)
4876 * @see #getLineSpacingExtra()
4878 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4880 public float getLineSpacingMultiplier() {
4881 return mSpacingMult;
4885 * Gets the line spacing extra space
4887 * @return the extra space that is added to the height of each lines of this TextView.
4889 * @see #setLineSpacing(float, float)
4890 * @see #getLineSpacingMultiplier()
4892 * @attr ref android.R.styleable#TextView_lineSpacingExtra
4894 public float getLineSpacingExtra() {
4899 * Convenience method to append the specified text to the TextView's
4900 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4901 * if it was not already editable.
4903 * @param text text to be appended to the already displayed text
4905 public final void append(CharSequence text) {
4906 append(text, 0, text.length());
4910 * Convenience method to append the specified text slice to the TextView's
4911 * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
4912 * if it was not already editable.
4914 * @param text text to be appended to the already displayed text
4915 * @param start the index of the first character in the {@code text}
4916 * @param end the index of the character following the last character in the {@code text}
4918 * @see Appendable#append(CharSequence, int, int)
4920 public void append(CharSequence text, int start, int end) {
4921 if (!(mText instanceof Editable)) {
4922 setText(mText, BufferType.EDITABLE);
4925 ((Editable) mText).append(text, start, end);
4927 if (mAutoLinkMask != 0) {
4928 boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4929 // Do not change the movement method for text that support text selection as it
4930 // would prevent an arbitrary cursor displacement.
4931 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
4932 setMovementMethod(LinkMovementMethod.getInstance());
4937 private void updateTextColors() {
4938 boolean inval = false;
4939 int color = mTextColor.getColorForState(getDrawableState(), 0);
4940 if (color != mCurTextColor) {
4941 mCurTextColor = color;
4944 if (mLinkTextColor != null) {
4945 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4946 if (color != mTextPaint.linkColor) {
4947 mTextPaint.linkColor = color;
4951 if (mHintTextColor != null) {
4952 color = mHintTextColor.getColorForState(getDrawableState(), 0);
4953 if (color != mCurHintTextColor) {
4954 mCurHintTextColor = color;
4955 if (mText.length() == 0) {
4961 // Text needs to be redrawn with the new color
4962 if (mEditor != null) mEditor.invalidateTextDisplayList();
4968 protected void drawableStateChanged() {
4969 super.drawableStateChanged();
4971 if (mTextColor != null && mTextColor.isStateful()
4972 || (mHintTextColor != null && mHintTextColor.isStateful())
4973 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4977 if (mDrawables != null) {
4978 final int[] state = getDrawableState();
4979 for (Drawable dr : mDrawables.mShowing) {
4980 if (dr != null && dr.isStateful() && dr.setState(state)) {
4981 invalidateDrawable(dr);
4988 public void drawableHotspotChanged(float x, float y) {
4989 super.drawableHotspotChanged(x, y);
4991 if (mDrawables != null) {
4992 for (Drawable dr : mDrawables.mShowing) {
4994 dr.setHotspot(x, y);
5001 public Parcelable onSaveInstanceState() {
5002 Parcelable superState = super.onSaveInstanceState();
5004 // Save state if we are forced to
5005 final boolean freezesText = getFreezesText();
5006 boolean hasSelection = false;
5010 if (mText != null) {
5011 start = getSelectionStart();
5012 end = getSelectionEnd();
5013 if (start >= 0 || end >= 0) {
5014 // Or save state if there is a selection
5015 hasSelection = true;
5019 if (freezesText || hasSelection) {
5020 SavedState ss = new SavedState(superState);
5023 if (mText instanceof Spanned) {
5024 final Spannable sp = new SpannableStringBuilder(mText);
5026 if (mEditor != null) {
5027 removeMisspelledSpans(sp);
5028 sp.removeSpan(mEditor.mSuggestionRangeSpan);
5033 ss.text = mText.toString();
5038 // XXX Should also save the current scroll position!
5039 ss.selStart = start;
5043 if (isFocused() && start >= 0 && end >= 0) {
5044 ss.frozenWithFocus = true;
5047 ss.error = getError();
5049 if (mEditor != null) {
5050 ss.editorState = mEditor.saveInstanceState();
5058 void removeMisspelledSpans(Spannable spannable) {
5059 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
5060 SuggestionSpan.class);
5061 for (int i = 0; i < suggestionSpans.length; i++) {
5062 int flags = suggestionSpans[i].getFlags();
5063 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
5064 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
5065 spannable.removeSpan(suggestionSpans[i]);
5071 public void onRestoreInstanceState(Parcelable state) {
5072 if (!(state instanceof SavedState)) {
5073 super.onRestoreInstanceState(state);
5077 SavedState ss = (SavedState) state;
5078 super.onRestoreInstanceState(ss.getSuperState());
5080 // XXX restore buffer type too, as well as lots of other stuff
5081 if (ss.text != null) {
5085 if (ss.selStart >= 0 && ss.selEnd >= 0) {
5086 if (mText instanceof Spannable) {
5087 int len = mText.length();
5089 if (ss.selStart > len || ss.selEnd > len) {
5090 String restored = "";
5092 if (ss.text != null) {
5093 restored = "(restored) ";
5096 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
5097 + " out of range for " + restored + "text " + mText);
5099 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
5101 if (ss.frozenWithFocus) {
5102 createEditorIfNeeded();
5103 mEditor.mFrozenWithFocus = true;
5109 if (ss.error != null) {
5110 final CharSequence error = ss.error;
5111 // Display the error later, after the first layout pass
5112 post(new Runnable() {
5114 if (mEditor == null || !mEditor.mErrorWasChanged) {
5121 if (ss.editorState != null) {
5122 createEditorIfNeeded();
5123 mEditor.restoreInstanceState(ss.editorState);
5128 * Control whether this text view saves its entire text contents when
5129 * freezing to an icicle, in addition to dynamic state such as cursor
5130 * position. By default this is false, not saving the text. Set to true
5131 * if the text in the text view is not being saved somewhere else in
5132 * persistent storage (such as in a content provider) so that if the
5133 * view is later thawed the user will not lose their data. For
5134 * {@link android.widget.EditText} it is always enabled, regardless of
5135 * the value of the attribute.
5137 * @param freezesText Controls whether a frozen icicle should include the
5138 * entire text data: true to include it, false to not.
5140 * @attr ref android.R.styleable#TextView_freezesText
5142 @android.view.RemotableViewMethod
5143 public void setFreezesText(boolean freezesText) {
5144 mFreezesText = freezesText;
5148 * Return whether this text view is including its entire text contents
5149 * in frozen icicles. For {@link android.widget.EditText} it always returns true.
5151 * @return Returns true if text is included, false if it isn't.
5153 * @see #setFreezesText
5155 public boolean getFreezesText() {
5156 return mFreezesText;
5159 ///////////////////////////////////////////////////////////////////////////
5162 * Sets the Factory used to create new {@link Editable Editables}.
5164 * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used
5166 * @see android.text.Editable.Factory
5167 * @see android.widget.TextView.BufferType#EDITABLE
5169 public final void setEditableFactory(Editable.Factory factory) {
5170 mEditableFactory = factory;
5175 * Sets the Factory used to create new {@link Spannable Spannables}.
5177 * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used
5179 * @see android.text.Spannable.Factory
5180 * @see android.widget.TextView.BufferType#SPANNABLE
5182 public final void setSpannableFactory(Spannable.Factory factory) {
5183 mSpannableFactory = factory;
5188 * Sets the text to be displayed. TextView <em>does not</em> accept
5189 * HTML-like formatting, which you can do with text strings in XML resource files.
5190 * To style your strings, attach android.text.style.* objects to a
5191 * {@link android.text.SpannableString}, or see the
5192 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
5193 * Available Resource Types</a> documentation for an example of setting
5194 * formatted text in the XML resource file.
5196 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5197 * intermediate {@link Spannable Spannables}. Likewise it will use
5198 * {@link android.text.Editable.Factory} to create final or intermediate
5199 * {@link Editable Editables}.
5201 * @param text text to be displayed
5203 * @attr ref android.R.styleable#TextView_text
5205 @android.view.RemotableViewMethod
5206 public final void setText(CharSequence text) {
5207 setText(text, mBufferType);
5211 * Sets the text to be displayed but retains the cursor position. Same as
5212 * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
5215 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5216 * intermediate {@link Spannable Spannables}. Likewise it will use
5217 * {@link android.text.Editable.Factory} to create final or intermediate
5218 * {@link Editable Editables}.
5220 * @param text text to be displayed
5222 * @see #setText(CharSequence)
5224 @android.view.RemotableViewMethod
5225 public final void setTextKeepState(CharSequence text) {
5226 setTextKeepState(text, mBufferType);
5230 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}.
5232 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5233 * intermediate {@link Spannable Spannables}. Likewise it will use
5234 * {@link android.text.Editable.Factory} to create final or intermediate
5235 * {@link Editable Editables}.
5237 * @param text text to be displayed
5238 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5239 * stored as a static text, styleable/spannable text, or editable text
5241 * @see #setText(CharSequence)
5242 * @see android.widget.TextView.BufferType
5243 * @see #setSpannableFactory(Spannable.Factory)
5244 * @see #setEditableFactory(Editable.Factory)
5246 * @attr ref android.R.styleable#TextView_text
5247 * @attr ref android.R.styleable#TextView_bufferType
5249 public void setText(CharSequence text, BufferType type) {
5250 setText(text, type, true, 0);
5252 if (mCharWrapper != null) {
5253 mCharWrapper.mChars = null;
5257 private void setText(CharSequence text, BufferType type,
5258 boolean notifyBefore, int oldlen) {
5259 mTextFromResource = false;
5264 // If suggestions are not enabled, remove the suggestion spans from the text
5265 if (!isSuggestionsEnabled()) {
5266 text = removeSuggestionSpans(text);
5269 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
5271 if (text instanceof Spanned
5272 && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
5273 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
5274 setHorizontalFadingEdgeEnabled(true);
5275 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
5277 setHorizontalFadingEdgeEnabled(false);
5278 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
5280 setEllipsize(TextUtils.TruncateAt.MARQUEE);
5283 int n = mFilters.length;
5284 for (int i = 0; i < n; i++) {
5285 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
5292 if (mText != null) {
5293 oldlen = mText.length();
5294 sendBeforeTextChanged(mText, 0, oldlen, text.length());
5296 sendBeforeTextChanged("", 0, 0, text.length());
5300 boolean needEditableForNotification = false;
5302 if (mListeners != null && mListeners.size() != 0) {
5303 needEditableForNotification = true;
5306 if (type == BufferType.EDITABLE || getKeyListener() != null
5307 || needEditableForNotification) {
5308 createEditorIfNeeded();
5309 mEditor.forgetUndoRedo();
5310 Editable t = mEditableFactory.newEditable(text);
5312 setFilters(t, mFilters);
5313 InputMethodManager imm = InputMethodManager.peekInstance();
5314 if (imm != null) imm.restartInput(this);
5315 } else if (type == BufferType.SPANNABLE || mMovement != null) {
5316 text = mSpannableFactory.newSpannable(text);
5317 } else if (!(text instanceof CharWrapper)) {
5318 text = TextUtils.stringOrSpannedString(text);
5321 if (mAutoLinkMask != 0) {
5324 if (type == BufferType.EDITABLE || text instanceof Spannable) {
5325 s2 = (Spannable) text;
5327 s2 = mSpannableFactory.newSpannable(text);
5330 if (Linkify.addLinks(s2, mAutoLinkMask)) {
5332 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
5335 * We must go ahead and set the text before changing the
5336 * movement method, because setMovementMethod() may call
5337 * setText() again to try to upgrade the buffer type.
5341 // Do not change the movement method for text that support text selection as it
5342 // would prevent an arbitrary cursor displacement.
5343 if (mLinksClickable && !textCanBeSelected()) {
5344 setMovementMethod(LinkMovementMethod.getInstance());
5352 if (mTransformation == null) {
5353 mTransformed = text;
5355 mTransformed = mTransformation.getTransformation(text, this);
5358 final int textLength = text.length();
5360 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
5361 Spannable sp = (Spannable) text;
5363 // Remove any ChangeWatchers that might have come from other TextViews.
5364 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
5365 final int count = watchers.length;
5366 for (int i = 0; i < count; i++) {
5367 sp.removeSpan(watchers[i]);
5370 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
5372 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE
5373 | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
5375 if (mEditor != null) mEditor.addSpanWatchers(sp);
5377 if (mTransformation != null) {
5378 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
5381 if (mMovement != null) {
5382 mMovement.initialize(this, (Spannable) text);
5385 * Initializing the movement method will have set the
5386 * selection, so reset mSelectionMoved to keep that from
5387 * interfering with the normal on-focus selection-setting.
5389 if (mEditor != null) mEditor.mSelectionMoved = false;
5393 if (mLayout != null) {
5397 sendOnTextChanged(text, 0, oldlen, textLength);
5398 onTextChanged(text, 0, oldlen, textLength);
5400 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
5402 if (needEditableForNotification) {
5403 sendAfterTextChanged((Editable) text);
5405 // Always notify AutoFillManager - it will return right away if autofill is disabled.
5406 notifyAutoFillManagerAfterTextChangedIfNeeded();
5409 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
5410 if (mEditor != null) mEditor.prepareCursorControllers();
5414 * Sets the TextView to display the specified slice of the specified
5415 * char array. You must promise that you will not change the contents
5416 * of the array except for right before another call to setText(),
5417 * since the TextView has no way to know that the text
5418 * has changed and that it needs to invalidate and re-layout.
5420 * @param text char array to be displayed
5421 * @param start start index in the char array
5422 * @param len length of char count after {@code start}
5424 public final void setText(char[] text, int start, int len) {
5427 if (start < 0 || len < 0 || start + len > text.length) {
5428 throw new IndexOutOfBoundsException(start + ", " + len);
5432 * We must do the before-notification here ourselves because if
5433 * the old text is a CharWrapper we destroy it before calling
5434 * into the normal path.
5436 if (mText != null) {
5437 oldlen = mText.length();
5438 sendBeforeTextChanged(mText, 0, oldlen, len);
5440 sendBeforeTextChanged("", 0, 0, len);
5443 if (mCharWrapper == null) {
5444 mCharWrapper = new CharWrapper(text, start, len);
5446 mCharWrapper.set(text, start, len);
5449 setText(mCharWrapper, mBufferType, false, oldlen);
5453 * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains
5454 * the cursor position. Same as
5455 * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor
5456 * position (if any) is retained in the new text.
5458 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5459 * intermediate {@link Spannable Spannables}. Likewise it will use
5460 * {@link android.text.Editable.Factory} to create final or intermediate
5461 * {@link Editable Editables}.
5463 * @param text text to be displayed
5464 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5465 * stored as a static text, styleable/spannable text, or editable text
5467 * @see #setText(CharSequence, android.widget.TextView.BufferType)
5469 public final void setTextKeepState(CharSequence text, BufferType type) {
5470 int start = getSelectionStart();
5471 int end = getSelectionEnd();
5472 int len = text.length();
5474 setText(text, type);
5476 if (start >= 0 || end >= 0) {
5477 if (mText instanceof Spannable) {
5478 Selection.setSelection((Spannable) mText,
5479 Math.max(0, Math.min(start, len)),
5480 Math.max(0, Math.min(end, len)));
5486 * Sets the text to be displayed using a string resource identifier.
5488 * @param resid the resource identifier of the string resource to be displayed
5490 * @see #setText(CharSequence)
5492 * @attr ref android.R.styleable#TextView_text
5494 @android.view.RemotableViewMethod
5495 public final void setText(@StringRes int resid) {
5496 setText(getContext().getResources().getText(resid));
5497 mTextFromResource = true;
5501 * Sets the text to be displayed using a string resource identifier and the
5502 * {@link android.widget.TextView.BufferType}.
5504 * When required, TextView will use {@link android.text.Spannable.Factory} to create final or
5505 * intermediate {@link Spannable Spannables}. Likewise it will use
5506 * {@link android.text.Editable.Factory} to create final or intermediate
5507 * {@link Editable Editables}.
5509 * @param resid the resource identifier of the string resource to be displayed
5510 * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is
5511 * stored as a static text, styleable/spannable text, or editable text
5513 * @see #setText(int)
5514 * @see #setText(CharSequence)
5515 * @see android.widget.TextView.BufferType
5516 * @see #setSpannableFactory(Spannable.Factory)
5517 * @see #setEditableFactory(Editable.Factory)
5519 * @attr ref android.R.styleable#TextView_text
5520 * @attr ref android.R.styleable#TextView_bufferType
5522 public final void setText(@StringRes int resid, BufferType type) {
5523 setText(getContext().getResources().getText(resid), type);
5524 mTextFromResource = true;
5528 * Sets the text to be displayed when the text of the TextView is empty.
5529 * Null means to use the normal empty text. The hint does not currently
5530 * participate in determining the size of the view.
5532 * @attr ref android.R.styleable#TextView_hint
5534 @android.view.RemotableViewMethod
5535 public final void setHint(CharSequence hint) {
5536 mHint = TextUtils.stringOrSpannedString(hint);
5538 if (mLayout != null) {
5542 if (mText.length() == 0) {
5546 // Invalidate display list if hint is currently used
5547 if (mEditor != null && mText.length() == 0 && mHint != null) {
5548 mEditor.invalidateTextDisplayList();
5553 * Sets the text to be displayed when the text of the TextView is empty,
5556 * @attr ref android.R.styleable#TextView_hint
5558 @android.view.RemotableViewMethod
5559 public final void setHint(@StringRes int resid) {
5560 setHint(getContext().getResources().getText(resid));
5564 * Returns the hint that is displayed when the text of the TextView
5567 * @attr ref android.R.styleable#TextView_hint
5569 @ViewDebug.CapturedViewProperty
5570 public CharSequence getHint() {
5574 boolean isSingleLine() {
5578 private static boolean isMultilineInputType(int type) {
5579 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
5580 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
5584 * Removes the suggestion spans.
5586 CharSequence removeSuggestionSpans(CharSequence text) {
5587 if (text instanceof Spanned) {
5588 Spannable spannable;
5589 if (text instanceof Spannable) {
5590 spannable = (Spannable) text;
5592 spannable = mSpannableFactory.newSpannable(text);
5596 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
5597 for (int i = 0; i < spans.length; i++) {
5598 spannable.removeSpan(spans[i]);
5605 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
5606 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
5607 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
5608 * then a soft keyboard will not be displayed for this text view.
5610 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
5611 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
5614 * @see #getInputType()
5615 * @see #setRawInputType(int)
5616 * @see android.text.InputType
5617 * @attr ref android.R.styleable#TextView_inputType
5619 public void setInputType(int type) {
5620 final boolean wasPassword = isPasswordInputType(getInputType());
5621 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
5622 setInputType(type, false);
5623 final boolean isPassword = isPasswordInputType(type);
5624 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
5625 boolean forceUpdate = false;
5627 setTransformationMethod(PasswordTransformationMethod.getInstance());
5628 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5629 } else if (isVisiblePassword) {
5630 if (mTransformation == PasswordTransformationMethod.getInstance()) {
5633 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, 0);
5634 } else if (wasPassword || wasVisiblePassword) {
5635 // not in password mode, clean up typeface and transformation
5636 setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, -1, -1);
5637 if (mTransformation == PasswordTransformationMethod.getInstance()) {
5642 boolean singleLine = !isMultilineInputType(type);
5644 // We need to update the single line mode if it has changed or we
5645 // were previously in password mode.
5646 if (mSingleLine != singleLine || forceUpdate) {
5647 // Change single line mode, but only change the transformation if
5648 // we are not in password mode.
5649 applySingleLine(singleLine, !isPassword, true);
5652 if (!isSuggestionsEnabled()) {
5653 mText = removeSuggestionSpans(mText);
5656 InputMethodManager imm = InputMethodManager.peekInstance();
5657 if (imm != null) imm.restartInput(this);
5661 * It would be better to rely on the input type for everything. A password inputType should have
5662 * a password transformation. We should hence use isPasswordInputType instead of this method.
5665 * - Call setInputType in setKeyListener instead of changing the input type directly (which
5666 * would install the correct transformation).
5667 * - Refuse the installation of a non-password transformation in setTransformation if the input
5670 * However, this is like this for legacy reasons and we cannot break existing apps. This method
5671 * is useful since it matches what the user can see (obfuscated text or not).
5673 * @return true if the current transformation method is of the password type.
5675 boolean hasPasswordTransformationMethod() {
5676 return mTransformation instanceof PasswordTransformationMethod;
5679 static boolean isPasswordInputType(int inputType) {
5680 final int variation =
5681 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5683 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
5685 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
5687 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
5690 private static boolean isVisiblePasswordInputType(int inputType) {
5691 final int variation =
5692 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
5694 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
5698 * Directly change the content type integer of the text view, without
5699 * modifying any other state.
5700 * @see #setInputType(int)
5701 * @see android.text.InputType
5702 * @attr ref android.R.styleable#TextView_inputType
5704 public void setRawInputType(int type) {
5705 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
5706 createEditorIfNeeded();
5707 mEditor.mInputType = type;
5711 * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise
5712 * a {@code Locale} object that can be used to customize key various listeners.
5713 * @see DateKeyListener#getInstance(Locale)
5714 * @see DateTimeKeyListener#getInstance(Locale)
5715 * @see DigitsKeyListener#getInstance(Locale)
5716 * @see TimeKeyListener#getInstance(Locale)
5719 private Locale getCustomLocaleForKeyListenerOrNull() {
5720 if (!mUseInternationalizedInput) {
5721 // If the application does not target O, stick to the previous behavior.
5724 final LocaleList locales = getImeHintLocales();
5725 if (locales == null) {
5726 // If the application does not explicitly specify IME hint locale, also stick to the
5727 // previous behavior.
5730 return locales.get(0);
5733 private void setInputType(int type, boolean direct) {
5734 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
5736 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
5737 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
5738 TextKeyListener.Capitalize cap;
5739 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
5740 cap = TextKeyListener.Capitalize.CHARACTERS;
5741 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
5742 cap = TextKeyListener.Capitalize.WORDS;
5743 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
5744 cap = TextKeyListener.Capitalize.SENTENCES;
5746 cap = TextKeyListener.Capitalize.NONE;
5748 input = TextKeyListener.getInstance(autotext, cap);
5749 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
5750 final Locale locale = getCustomLocaleForKeyListenerOrNull();
5751 input = DigitsKeyListener.getInstance(
5753 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
5754 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
5755 if (locale != null) {
5756 // Override type, if necessary for i18n.
5757 int newType = input.getInputType();
5758 final int newClass = newType & EditorInfo.TYPE_MASK_CLASS;
5759 if (newClass != EditorInfo.TYPE_CLASS_NUMBER) {
5760 // The class is different from the original class. So we need to override
5761 // 'type'. But we want to keep the password flag if it's there.
5762 if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) {
5763 newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
5768 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
5769 final Locale locale = getCustomLocaleForKeyListenerOrNull();
5770 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
5771 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
5772 input = DateKeyListener.getInstance(locale);
5774 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
5775 input = TimeKeyListener.getInstance(locale);
5778 input = DateTimeKeyListener.getInstance(locale);
5781 if (mUseInternationalizedInput) {
5782 type = input.getInputType(); // Override type, if necessary for i18n.
5784 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
5785 input = DialerKeyListener.getInstance();
5787 input = TextKeyListener.getInstance();
5789 setRawInputType(type);
5790 mListenerChanged = false;
5792 createEditorIfNeeded();
5793 mEditor.mKeyListener = input;
5795 setKeyListenerOnly(input);
5800 * Get the type of the editable content.
5802 * @see #setInputType(int)
5803 * @see android.text.InputType
5805 public int getInputType() {
5806 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
5810 * Change the editor type integer associated with the text view, which
5811 * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
5812 * when it has focus.
5813 * @see #getImeOptions
5814 * @see android.view.inputmethod.EditorInfo
5815 * @attr ref android.R.styleable#TextView_imeOptions
5817 public void setImeOptions(int imeOptions) {
5818 createEditorIfNeeded();
5819 mEditor.createInputContentTypeIfNeeded();
5820 mEditor.mInputContentType.imeOptions = imeOptions;
5824 * Get the type of the Input Method Editor (IME).
5825 * @return the type of the IME
5826 * @see #setImeOptions(int)
5827 * @see android.view.inputmethod.EditorInfo
5829 public int getImeOptions() {
5830 return mEditor != null && mEditor.mInputContentType != null
5831 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
5835 * Change the custom IME action associated with the text view, which
5836 * will be reported to an IME with {@link EditorInfo#actionLabel}
5837 * and {@link EditorInfo#actionId} when it has focus.
5838 * @see #getImeActionLabel
5839 * @see #getImeActionId
5840 * @see android.view.inputmethod.EditorInfo
5841 * @attr ref android.R.styleable#TextView_imeActionLabel
5842 * @attr ref android.R.styleable#TextView_imeActionId
5844 public void setImeActionLabel(CharSequence label, int actionId) {
5845 createEditorIfNeeded();
5846 mEditor.createInputContentTypeIfNeeded();
5847 mEditor.mInputContentType.imeActionLabel = label;
5848 mEditor.mInputContentType.imeActionId = actionId;
5852 * Get the IME action label previous set with {@link #setImeActionLabel}.
5854 * @see #setImeActionLabel
5855 * @see android.view.inputmethod.EditorInfo
5857 public CharSequence getImeActionLabel() {
5858 return mEditor != null && mEditor.mInputContentType != null
5859 ? mEditor.mInputContentType.imeActionLabel : null;
5863 * Get the IME action ID previous set with {@link #setImeActionLabel}.
5865 * @see #setImeActionLabel
5866 * @see android.view.inputmethod.EditorInfo
5868 public int getImeActionId() {
5869 return mEditor != null && mEditor.mInputContentType != null
5870 ? mEditor.mInputContentType.imeActionId : 0;
5874 * Set a special listener to be called when an action is performed
5875 * on the text view. This will be called when the enter key is pressed,
5876 * or when an action supplied to the IME is selected by the user. Setting
5877 * this means that the normal hard key event will not insert a newline
5878 * into the text view, even if it is multi-line; holding down the ALT
5879 * modifier will, however, allow the user to insert a newline character.
5881 public void setOnEditorActionListener(OnEditorActionListener l) {
5882 createEditorIfNeeded();
5883 mEditor.createInputContentTypeIfNeeded();
5884 mEditor.mInputContentType.onEditorActionListener = l;
5888 * Called when an attached input method calls
5889 * {@link InputConnection#performEditorAction(int)
5890 * InputConnection.performEditorAction()}
5891 * for this text view. The default implementation will call your action
5892 * listener supplied to {@link #setOnEditorActionListener}, or perform
5893 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
5894 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
5895 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
5896 * EditorInfo.IME_ACTION_DONE}.
5898 * <p>For backwards compatibility, if no IME options have been set and the
5899 * text view would not normally advance focus on enter, then
5900 * the NEXT and DONE actions received here will be turned into an enter
5901 * key down/up pair to go through the normal key handling.
5903 * @param actionCode The code of the action being performed.
5905 * @see #setOnEditorActionListener
5907 public void onEditorAction(int actionCode) {
5908 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
5910 if (ict.onEditorActionListener != null) {
5911 if (ict.onEditorActionListener.onEditorAction(this,
5912 actionCode, null)) {
5917 // This is the handling for some default action.
5918 // Note that for backwards compatibility we don't do this
5919 // default handling if explicit ime options have not been given,
5920 // instead turning this into the normal enter key codes that an
5921 // app may be expecting.
5922 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
5923 View v = focusSearch(FOCUS_FORWARD);
5925 if (!v.requestFocus(FOCUS_FORWARD)) {
5926 throw new IllegalStateException("focus search returned a view "
5927 + "that wasn't able to take focus!");
5932 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
5933 View v = focusSearch(FOCUS_BACKWARD);
5935 if (!v.requestFocus(FOCUS_BACKWARD)) {
5936 throw new IllegalStateException("focus search returned a view "
5937 + "that wasn't able to take focus!");
5942 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
5943 InputMethodManager imm = InputMethodManager.peekInstance();
5944 if (imm != null && imm.isActive(this)) {
5945 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5951 ViewRootImpl viewRootImpl = getViewRootImpl();
5952 if (viewRootImpl != null) {
5953 long eventTime = SystemClock.uptimeMillis();
5954 viewRootImpl.dispatchKeyFromIme(
5955 new KeyEvent(eventTime, eventTime,
5956 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
5957 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5958 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5959 | KeyEvent.FLAG_EDITOR_ACTION));
5960 viewRootImpl.dispatchKeyFromIme(
5961 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
5962 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
5963 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
5964 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
5965 | KeyEvent.FLAG_EDITOR_ACTION));
5970 * Set the private content type of the text, which is the
5971 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
5972 * field that will be filled in when creating an input connection.
5974 * @see #getPrivateImeOptions()
5975 * @see EditorInfo#privateImeOptions
5976 * @attr ref android.R.styleable#TextView_privateImeOptions
5978 public void setPrivateImeOptions(String type) {
5979 createEditorIfNeeded();
5980 mEditor.createInputContentTypeIfNeeded();
5981 mEditor.mInputContentType.privateImeOptions = type;
5985 * Get the private type of the content.
5987 * @see #setPrivateImeOptions(String)
5988 * @see EditorInfo#privateImeOptions
5990 public String getPrivateImeOptions() {
5991 return mEditor != null && mEditor.mInputContentType != null
5992 ? mEditor.mInputContentType.privateImeOptions : null;
5996 * Set the extra input data of the text, which is the
5997 * {@link EditorInfo#extras TextBoxAttribute.extras}
5998 * Bundle that will be filled in when creating an input connection. The
5999 * given integer is the resource identifier of an XML resource holding an
6000 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
6002 * @see #getInputExtras(boolean)
6003 * @see EditorInfo#extras
6004 * @attr ref android.R.styleable#TextView_editorExtras
6006 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
6007 createEditorIfNeeded();
6008 XmlResourceParser parser = getResources().getXml(xmlResId);
6009 mEditor.createInputContentTypeIfNeeded();
6010 mEditor.mInputContentType.extras = new Bundle();
6011 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
6015 * Retrieve the input extras currently associated with the text view, which
6016 * can be viewed as well as modified.
6018 * @param create If true, the extras will be created if they don't already
6019 * exist. Otherwise, null will be returned if none have been created.
6020 * @see #setInputExtras(int)
6021 * @see EditorInfo#extras
6022 * @attr ref android.R.styleable#TextView_editorExtras
6024 public Bundle getInputExtras(boolean create) {
6025 if (mEditor == null && !create) return null;
6026 createEditorIfNeeded();
6027 if (mEditor.mInputContentType == null) {
6028 if (!create) return null;
6029 mEditor.createInputContentTypeIfNeeded();
6031 if (mEditor.mInputContentType.extras == null) {
6032 if (!create) return null;
6033 mEditor.mInputContentType.extras = new Bundle();
6035 return mEditor.mInputContentType.extras;
6039 * Change "hint" locales associated with the text view, which will be reported to an IME with
6040 * {@link EditorInfo#hintLocales} when it has focus.
6042 * Starting with Android O, this also causes internationalized listeners to be created (or
6043 * change locale) based on the first locale in the input locale list.
6045 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
6046 * call {@link InputMethodManager#restartInput(View)}.</p>
6047 * @param hintLocales List of the languages that the user is supposed to switch to no matter
6048 * what input method subtype is currently used. Set {@code null} to clear the current "hint".
6049 * @see #getImeHintLocales()
6050 * @see android.view.inputmethod.EditorInfo#hintLocales
6052 public void setImeHintLocales(@Nullable LocaleList hintLocales) {
6053 createEditorIfNeeded();
6054 mEditor.createInputContentTypeIfNeeded();
6055 mEditor.mInputContentType.imeHintLocales = hintLocales;
6056 if (mUseInternationalizedInput) {
6057 changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0));
6062 * @return The current languages list "hint". {@code null} when no "hint" is available.
6063 * @see #setImeHintLocales(LocaleList)
6064 * @see android.view.inputmethod.EditorInfo#hintLocales
6067 public LocaleList getImeHintLocales() {
6068 if (mEditor == null) {
6071 if (mEditor.mInputContentType == null) {
6074 return mEditor.mInputContentType.imeHintLocales;
6078 * Returns the error message that was set to be displayed with
6079 * {@link #setError}, or <code>null</code> if no error was set
6080 * or if it the error was cleared by the widget after user input.
6082 public CharSequence getError() {
6083 return mEditor == null ? null : mEditor.mError;
6087 * Sets the right-hand compound drawable of the TextView to the "error"
6088 * icon and sets an error message that will be displayed in a popup when
6089 * the TextView has focus. The icon and error message will be reset to
6090 * null when any key events cause changes to the TextView's text. If the
6091 * <code>error</code> is <code>null</code>, the error message and icon
6094 @android.view.RemotableViewMethod
6095 public void setError(CharSequence error) {
6096 if (error == null) {
6097 setError(null, null);
6099 Drawable dr = getContext().getDrawable(
6100 com.android.internal.R.drawable.indicator_input_error);
6102 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
6103 setError(error, dr);
6108 * Sets the right-hand compound drawable of the TextView to the specified
6109 * icon and sets an error message that will be displayed in a popup when
6110 * the TextView has focus. The icon and error message will be reset to
6111 * null when any key events cause changes to the TextView's text. The
6112 * drawable must already have had {@link Drawable#setBounds} set on it.
6113 * If the <code>error</code> is <code>null</code>, the error message will
6114 * be cleared (and you should provide a <code>null</code> icon as well).
6116 public void setError(CharSequence error, Drawable icon) {
6117 createEditorIfNeeded();
6118 mEditor.setError(error, icon);
6119 notifyViewAccessibilityStateChangedIfNeeded(
6120 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
6124 protected boolean setFrame(int l, int t, int r, int b) {
6125 boolean result = super.setFrame(l, t, r, b);
6127 if (mEditor != null) mEditor.setFrame();
6129 restartMarqueeIfNeeded();
6134 private void restartMarqueeIfNeeded() {
6135 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6136 mRestartMarquee = false;
6142 * Sets the list of input filters that will be used if the buffer is
6143 * Editable. Has no effect otherwise.
6145 * @attr ref android.R.styleable#TextView_maxLength
6147 public void setFilters(InputFilter[] filters) {
6148 if (filters == null) {
6149 throw new IllegalArgumentException();
6154 if (mText instanceof Editable) {
6155 setFilters((Editable) mText, filters);
6160 * Sets the list of input filters on the specified Editable,
6161 * and includes mInput in the list if it is an InputFilter.
6163 private void setFilters(Editable e, InputFilter[] filters) {
6164 if (mEditor != null) {
6165 final boolean undoFilter = mEditor.mUndoInputFilter != null;
6166 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
6168 if (undoFilter) num++;
6169 if (keyFilter) num++;
6171 InputFilter[] nf = new InputFilter[filters.length + num];
6173 System.arraycopy(filters, 0, nf, 0, filters.length);
6176 nf[filters.length] = mEditor.mUndoInputFilter;
6180 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
6187 e.setFilters(filters);
6191 * Returns the current list of input filters.
6193 * @attr ref android.R.styleable#TextView_maxLength
6195 public InputFilter[] getFilters() {
6199 /////////////////////////////////////////////////////////////////////////
6201 private int getBoxHeight(Layout l) {
6202 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
6203 int padding = (l == mHintLayout)
6204 ? getCompoundPaddingTop() + getCompoundPaddingBottom()
6205 : getExtendedPaddingTop() + getExtendedPaddingBottom();
6206 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
6209 int getVerticalOffset(boolean forceNormal) {
6211 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6214 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6218 if (gravity != Gravity.TOP) {
6219 int boxht = getBoxHeight(l);
6220 int textht = l.getHeight();
6222 if (textht < boxht) {
6223 if (gravity == Gravity.BOTTOM) {
6224 voffset = boxht - textht;
6225 } else { // (gravity == Gravity.CENTER_VERTICAL)
6226 voffset = (boxht - textht) >> 1;
6233 private int getBottomVerticalOffset(boolean forceNormal) {
6235 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
6238 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
6242 if (gravity != Gravity.BOTTOM) {
6243 int boxht = getBoxHeight(l);
6244 int textht = l.getHeight();
6246 if (textht < boxht) {
6247 if (gravity == Gravity.TOP) {
6248 voffset = boxht - textht;
6249 } else { // (gravity == Gravity.CENTER_VERTICAL)
6250 voffset = (boxht - textht) >> 1;
6257 void invalidateCursorPath() {
6258 if (mHighlightPathBogus) {
6261 final int horizontalPadding = getCompoundPaddingLeft();
6262 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6264 if (mEditor.mCursorCount == 0) {
6265 synchronized (TEMP_RECTF) {
6267 * The reason for this concern about the thickness of the
6268 * cursor and doing the floor/ceil on the coordinates is that
6269 * some EditTexts (notably textfields in the Browser) have
6270 * anti-aliased text where not all the characters are
6271 * necessarily at integer-multiple locations. This should
6272 * make sure the entire cursor gets invalidated instead of
6273 * sometimes missing half a pixel.
6275 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
6282 // mHighlightPath is guaranteed to be non null at that point.
6283 mHighlightPath.computeBounds(TEMP_RECTF, false);
6285 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
6286 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
6287 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
6288 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
6291 for (int i = 0; i < mEditor.mCursorCount; i++) {
6292 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6293 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
6294 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
6300 void invalidateCursor() {
6301 int where = getSelectionEnd();
6303 invalidateCursor(where, where, where);
6306 private void invalidateCursor(int a, int b, int c) {
6307 if (a >= 0 || b >= 0 || c >= 0) {
6308 int start = Math.min(Math.min(a, b), c);
6309 int end = Math.max(Math.max(a, b), c);
6310 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
6315 * Invalidates the region of text enclosed between the start and end text offsets.
6317 void invalidateRegion(int start, int end, boolean invalidateCursor) {
6318 if (mLayout == null) {
6321 int lineStart = mLayout.getLineForOffset(start);
6322 int top = mLayout.getLineTop(lineStart);
6324 // This is ridiculous, but the descent from the line above
6325 // can hang down into the line we really want to redraw,
6326 // so we have to invalidate part of the line above to make
6327 // sure everything that needs to be redrawn really is.
6328 // (But not the whole line above, because that would cause
6329 // the same problem with the descenders on the line above it!)
6330 if (lineStart > 0) {
6331 top -= mLayout.getLineDescent(lineStart - 1);
6337 lineEnd = lineStart;
6339 lineEnd = mLayout.getLineForOffset(end);
6342 int bottom = mLayout.getLineBottom(lineEnd);
6344 // mEditor can be null in case selection is set programmatically.
6345 if (invalidateCursor && mEditor != null) {
6346 for (int i = 0; i < mEditor.mCursorCount; i++) {
6347 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
6348 top = Math.min(top, bounds.top);
6349 bottom = Math.max(bottom, bounds.bottom);
6353 final int compoundPaddingLeft = getCompoundPaddingLeft();
6354 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
6357 if (lineStart == lineEnd && !invalidateCursor) {
6358 left = (int) mLayout.getPrimaryHorizontal(start);
6359 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
6360 left += compoundPaddingLeft;
6361 right += compoundPaddingLeft;
6363 // Rectangle bounding box when the region spans several lines
6364 left = compoundPaddingLeft;
6365 right = getWidth() - getCompoundPaddingRight();
6368 invalidate(mScrollX + left, verticalPadding + top,
6369 mScrollX + right, verticalPadding + bottom);
6373 private void registerForPreDraw() {
6374 if (!mPreDrawRegistered) {
6375 getViewTreeObserver().addOnPreDrawListener(this);
6376 mPreDrawRegistered = true;
6380 private void unregisterForPreDraw() {
6381 getViewTreeObserver().removeOnPreDrawListener(this);
6382 mPreDrawRegistered = false;
6383 mPreDrawListenerDetached = false;
6390 public boolean onPreDraw() {
6391 if (mLayout == null) {
6395 if (mMovement != null) {
6396 /* This code also provides auto-scrolling when a cursor is moved using a
6397 * CursorController (insertion point or selection limits).
6398 * For selection, ensure start or end is visible depending on controller's state.
6400 int curs = getSelectionEnd();
6401 // Do not create the controller if it is not already created.
6402 if (mEditor != null && mEditor.mSelectionModifierCursorController != null
6403 && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
6404 curs = getSelectionStart();
6408 * TODO: This should really only keep the end in view if
6409 * it already was before the text changed. I'm not sure
6410 * of a good way to tell from here if it was.
6412 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6413 curs = mText.length();
6417 bringPointIntoView(curs);
6420 bringTextIntoView();
6423 // This has to be checked here since:
6424 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
6425 // a screen rotation) since layout is not yet initialized at that point.
6426 if (mEditor != null && mEditor.mCreatedWithASelection) {
6427 mEditor.refreshTextActionMode();
6428 mEditor.mCreatedWithASelection = false;
6431 unregisterForPreDraw();
6437 protected void onAttachedToWindow() {
6438 super.onAttachedToWindow();
6440 if (mEditor != null) mEditor.onAttachedToWindow();
6442 if (mPreDrawListenerDetached) {
6443 getViewTreeObserver().addOnPreDrawListener(this);
6444 mPreDrawListenerDetached = false;
6450 protected void onDetachedFromWindowInternal() {
6451 if (mPreDrawRegistered) {
6452 getViewTreeObserver().removeOnPreDrawListener(this);
6453 mPreDrawListenerDetached = true;
6456 resetResolvedDrawables();
6458 if (mEditor != null) mEditor.onDetachedFromWindow();
6460 super.onDetachedFromWindowInternal();
6464 public void onScreenStateChanged(int screenState) {
6465 super.onScreenStateChanged(screenState);
6466 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
6470 protected boolean isPaddingOffsetRequired() {
6471 return mShadowRadius != 0 || mDrawables != null;
6475 protected int getLeftPaddingOffset() {
6476 return getCompoundPaddingLeft() - mPaddingLeft
6477 + (int) Math.min(0, mShadowDx - mShadowRadius);
6481 protected int getTopPaddingOffset() {
6482 return (int) Math.min(0, mShadowDy - mShadowRadius);
6486 protected int getBottomPaddingOffset() {
6487 return (int) Math.max(0, mShadowDy + mShadowRadius);
6491 protected int getRightPaddingOffset() {
6492 return -(getCompoundPaddingRight() - mPaddingRight)
6493 + (int) Math.max(0, mShadowDx + mShadowRadius);
6497 protected boolean verifyDrawable(@NonNull Drawable who) {
6498 final boolean verified = super.verifyDrawable(who);
6499 if (!verified && mDrawables != null) {
6500 for (Drawable dr : mDrawables.mShowing) {
6510 public void jumpDrawablesToCurrentState() {
6511 super.jumpDrawablesToCurrentState();
6512 if (mDrawables != null) {
6513 for (Drawable dr : mDrawables.mShowing) {
6515 dr.jumpToCurrentState();
6522 public void invalidateDrawable(@NonNull Drawable drawable) {
6523 boolean handled = false;
6525 if (verifyDrawable(drawable)) {
6526 final Rect dirty = drawable.getBounds();
6527 int scrollX = mScrollX;
6528 int scrollY = mScrollY;
6530 // IMPORTANT: The coordinates below are based on the coordinates computed
6531 // for each compound drawable in onDraw(). Make sure to update each section
6533 final TextView.Drawables drawables = mDrawables;
6534 if (drawables != null) {
6535 if (drawable == drawables.mShowing[Drawables.LEFT]) {
6536 final int compoundPaddingTop = getCompoundPaddingTop();
6537 final int compoundPaddingBottom = getCompoundPaddingBottom();
6538 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6540 scrollX += mPaddingLeft;
6541 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
6543 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
6544 final int compoundPaddingTop = getCompoundPaddingTop();
6545 final int compoundPaddingBottom = getCompoundPaddingBottom();
6546 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6548 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
6549 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
6551 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
6552 final int compoundPaddingLeft = getCompoundPaddingLeft();
6553 final int compoundPaddingRight = getCompoundPaddingRight();
6554 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6556 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
6557 scrollY += mPaddingTop;
6559 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
6560 final int compoundPaddingLeft = getCompoundPaddingLeft();
6561 final int compoundPaddingRight = getCompoundPaddingRight();
6562 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
6564 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
6565 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
6571 invalidate(dirty.left + scrollX, dirty.top + scrollY,
6572 dirty.right + scrollX, dirty.bottom + scrollY);
6577 super.invalidateDrawable(drawable);
6582 public boolean hasOverlappingRendering() {
6583 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
6584 return ((getBackground() != null && getBackground().getCurrent() != null)
6585 || mText instanceof Spannable || hasSelection()
6586 || isHorizontalFadingEdgeEnabled());
6591 * Returns the state of the {@code textIsSelectable} flag (See
6592 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
6593 * to allow users to select and copy text in a non-editable TextView, the content of an
6594 * {@link EditText} can always be selected, independently of the value of this flag.
6597 * @return True if the text displayed in this TextView can be selected by the user.
6599 * @attr ref android.R.styleable#TextView_textIsSelectable
6601 public boolean isTextSelectable() {
6602 return mEditor == null ? false : mEditor.mTextIsSelectable;
6606 * Sets whether the content of this view is selectable by the user. The default is
6607 * {@code false}, meaning that the content is not selectable.
6609 * When you use a TextView to display a useful piece of information to the user (such as a
6610 * contact's address), make it selectable, so that the user can select and copy its
6611 * content. You can also use set the XML attribute
6612 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
6614 * When you call this method to set the value of {@code textIsSelectable}, it sets
6615 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
6616 * and {@code longClickable} to the same value. These flags correspond to the attributes
6617 * {@link android.R.styleable#View_focusable android:focusable},
6618 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
6619 * {@link android.R.styleable#View_clickable android:clickable}, and
6620 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
6621 * flags to a state you had set previously, call one or more of the following methods:
6622 * {@link #setFocusable(boolean) setFocusable()},
6623 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
6624 * {@link #setClickable(boolean) setClickable()} or
6625 * {@link #setLongClickable(boolean) setLongClickable()}.
6627 * @param selectable Whether the content of this TextView should be selectable.
6629 public void setTextIsSelectable(boolean selectable) {
6630 if (!selectable && mEditor == null) return; // false is default value with no edit data
6632 createEditorIfNeeded();
6633 if (mEditor.mTextIsSelectable == selectable) return;
6635 mEditor.mTextIsSelectable = selectable;
6636 setFocusableInTouchMode(selectable);
6637 setFocusable(FOCUSABLE_AUTO);
6638 setClickable(selectable);
6639 setLongClickable(selectable);
6641 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
6643 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
6644 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
6646 // Called by setText above, but safer in case of future code changes
6647 mEditor.prepareCursorControllers();
6651 protected int[] onCreateDrawableState(int extraSpace) {
6652 final int[] drawableState;
6655 drawableState = super.onCreateDrawableState(extraSpace);
6657 drawableState = super.onCreateDrawableState(extraSpace + 1);
6658 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
6661 if (isTextSelectable()) {
6662 // Disable pressed state, which was introduced when TextView was made clickable.
6663 // Prevents text color change.
6664 // setClickable(false) would have a similar effect, but it also disables focus changes
6665 // and long press actions, which are both needed by text selection.
6666 final int length = drawableState.length;
6667 for (int i = 0; i < length; i++) {
6668 if (drawableState[i] == R.attr.state_pressed) {
6669 final int[] nonPressedState = new int[length - 1];
6670 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
6671 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
6672 return nonPressedState;
6677 return drawableState;
6680 private Path getUpdatedHighlightPath() {
6681 Path highlight = null;
6682 Paint highlightPaint = mHighlightPaint;
6684 final int selStart = getSelectionStart();
6685 final int selEnd = getSelectionEnd();
6686 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
6687 if (selStart == selEnd) {
6688 if (mEditor != null && mEditor.isCursorVisible()
6689 && (SystemClock.uptimeMillis() - mEditor.mShowCursor)
6690 % (2 * Editor.BLINK) < Editor.BLINK) {
6691 if (mHighlightPathBogus) {
6692 if (mHighlightPath == null) mHighlightPath = new Path();
6693 mHighlightPath.reset();
6694 mLayout.getCursorPath(selStart, mHighlightPath, mText);
6695 mEditor.updateCursorsPositions();
6696 mHighlightPathBogus = false;
6699 // XXX should pass to skin instead of drawing directly
6700 highlightPaint.setColor(mCurTextColor);
6701 highlightPaint.setStyle(Paint.Style.STROKE);
6702 highlight = mHighlightPath;
6705 if (mHighlightPathBogus) {
6706 if (mHighlightPath == null) mHighlightPath = new Path();
6707 mHighlightPath.reset();
6708 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6709 mHighlightPathBogus = false;
6712 // XXX should pass to skin instead of drawing directly
6713 highlightPaint.setColor(mHighlightColor);
6714 highlightPaint.setStyle(Paint.Style.FILL);
6716 highlight = mHighlightPath;
6725 public int getHorizontalOffsetForDrawables() {
6730 protected void onDraw(Canvas canvas) {
6731 restartMarqueeIfNeeded();
6733 // Draw the background for this view
6734 super.onDraw(canvas);
6736 final int compoundPaddingLeft = getCompoundPaddingLeft();
6737 final int compoundPaddingTop = getCompoundPaddingTop();
6738 final int compoundPaddingRight = getCompoundPaddingRight();
6739 final int compoundPaddingBottom = getCompoundPaddingBottom();
6740 final int scrollX = mScrollX;
6741 final int scrollY = mScrollY;
6742 final int right = mRight;
6743 final int left = mLeft;
6744 final int bottom = mBottom;
6745 final int top = mTop;
6746 final boolean isLayoutRtl = isLayoutRtl();
6747 final int offset = getHorizontalOffsetForDrawables();
6748 final int leftOffset = isLayoutRtl ? 0 : offset;
6749 final int rightOffset = isLayoutRtl ? offset : 0;
6751 final Drawables dr = mDrawables;
6754 * Compound, not extended, because the icon is not clipped
6755 * if the text height is smaller.
6758 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
6759 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
6761 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6762 // Make sure to update invalidateDrawable() when changing this code.
6763 if (dr.mShowing[Drawables.LEFT] != null) {
6765 canvas.translate(scrollX + mPaddingLeft + leftOffset,
6766 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
6767 dr.mShowing[Drawables.LEFT].draw(canvas);
6771 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6772 // Make sure to update invalidateDrawable() when changing this code.
6773 if (dr.mShowing[Drawables.RIGHT] != null) {
6775 canvas.translate(scrollX + right - left - mPaddingRight
6776 - dr.mDrawableSizeRight - rightOffset,
6777 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
6778 dr.mShowing[Drawables.RIGHT].draw(canvas);
6782 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6783 // Make sure to update invalidateDrawable() when changing this code.
6784 if (dr.mShowing[Drawables.TOP] != null) {
6786 canvas.translate(scrollX + compoundPaddingLeft
6787 + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
6788 dr.mShowing[Drawables.TOP].draw(canvas);
6792 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
6793 // Make sure to update invalidateDrawable() when changing this code.
6794 if (dr.mShowing[Drawables.BOTTOM] != null) {
6796 canvas.translate(scrollX + compoundPaddingLeft
6797 + (hspace - dr.mDrawableWidthBottom) / 2,
6798 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
6799 dr.mShowing[Drawables.BOTTOM].draw(canvas);
6804 int color = mCurTextColor;
6806 if (mLayout == null) {
6810 Layout layout = mLayout;
6812 if (mHint != null && mText.length() == 0) {
6813 if (mHintTextColor != null) {
6814 color = mCurHintTextColor;
6817 layout = mHintLayout;
6820 mTextPaint.setColor(color);
6821 mTextPaint.drawableState = getDrawableState();
6824 /* Would be faster if we didn't have to do this. Can we chop the
6825 (displayable) text so that we don't need to do this ever?
6828 int extendedPaddingTop = getExtendedPaddingTop();
6829 int extendedPaddingBottom = getExtendedPaddingBottom();
6831 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
6832 final int maxScrollY = mLayout.getHeight() - vspace;
6834 float clipLeft = compoundPaddingLeft + scrollX;
6835 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
6836 float clipRight = right - left - getCompoundPaddingRight() + scrollX;
6837 float clipBottom = bottom - top + scrollY
6838 - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
6840 if (mShadowRadius != 0) {
6841 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
6842 clipRight += Math.max(0, mShadowDx + mShadowRadius);
6844 clipTop += Math.min(0, mShadowDy - mShadowRadius);
6845 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
6848 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
6850 int voffsetText = 0;
6851 int voffsetCursor = 0;
6853 // translate in by our padding
6854 /* shortcircuit calling getVerticaOffset() */
6855 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6856 voffsetText = getVerticalOffset(false);
6857 voffsetCursor = getVerticalOffset(true);
6859 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
6861 final int layoutDirection = getLayoutDirection();
6862 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
6863 if (isMarqueeFadeEnabled()) {
6864 if (!mSingleLine && getLineCount() == 1 && canMarquee()
6865 && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
6866 final int width = mRight - mLeft;
6867 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
6868 final float dx = mLayout.getLineRight(0) - (width - padding);
6869 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6872 if (mMarquee != null && mMarquee.isRunning()) {
6873 final float dx = -mMarquee.getScroll();
6874 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6878 final int cursorOffsetVertical = voffsetCursor - voffsetText;
6880 Path highlight = getUpdatedHighlightPath();
6881 if (mEditor != null) {
6882 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
6884 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6887 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
6888 final float dx = mMarquee.getGhostOffset();
6889 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
6890 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
6897 public void getFocusedRect(Rect r) {
6898 if (mLayout == null) {
6899 super.getFocusedRect(r);
6903 int selEnd = getSelectionEnd();
6905 super.getFocusedRect(r);
6909 int selStart = getSelectionStart();
6910 if (selStart < 0 || selStart >= selEnd) {
6911 int line = mLayout.getLineForOffset(selEnd);
6912 r.top = mLayout.getLineTop(line);
6913 r.bottom = mLayout.getLineBottom(line);
6914 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
6915 r.right = r.left + 4;
6917 int lineStart = mLayout.getLineForOffset(selStart);
6918 int lineEnd = mLayout.getLineForOffset(selEnd);
6919 r.top = mLayout.getLineTop(lineStart);
6920 r.bottom = mLayout.getLineBottom(lineEnd);
6921 if (lineStart == lineEnd) {
6922 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
6923 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
6925 // Selection extends across multiple lines -- make the focused
6926 // rect cover the entire width.
6927 if (mHighlightPathBogus) {
6928 if (mHighlightPath == null) mHighlightPath = new Path();
6929 mHighlightPath.reset();
6930 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
6931 mHighlightPathBogus = false;
6933 synchronized (TEMP_RECTF) {
6934 mHighlightPath.computeBounds(TEMP_RECTF, true);
6935 r.left = (int) TEMP_RECTF.left - 1;
6936 r.right = (int) TEMP_RECTF.right + 1;
6941 // Adjust for padding and gravity.
6942 int paddingLeft = getCompoundPaddingLeft();
6943 int paddingTop = getExtendedPaddingTop();
6944 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6945 paddingTop += getVerticalOffset(false);
6947 r.offset(paddingLeft, paddingTop);
6948 int paddingBottom = getExtendedPaddingBottom();
6949 r.bottom += paddingBottom;
6953 * Return the number of lines of text, or 0 if the internal Layout has not
6956 public int getLineCount() {
6957 return mLayout != null ? mLayout.getLineCount() : 0;
6961 * Return the baseline for the specified line (0...getLineCount() - 1)
6962 * If bounds is not null, return the top, left, right, bottom extents
6963 * of the specified line in it. If the internal Layout has not been built,
6964 * return 0 and set bounds to (0, 0, 0, 0)
6965 * @param line which line to examine (0..getLineCount() - 1)
6966 * @param bounds Optional. If not null, it returns the extent of the line
6967 * @return the Y-coordinate of the baseline
6969 public int getLineBounds(int line, Rect bounds) {
6970 if (mLayout == null) {
6971 if (bounds != null) {
6972 bounds.set(0, 0, 0, 0);
6976 int baseline = mLayout.getLineBounds(line, bounds);
6978 int voffset = getExtendedPaddingTop();
6979 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
6980 voffset += getVerticalOffset(true);
6982 if (bounds != null) {
6983 bounds.offset(getCompoundPaddingLeft(), voffset);
6985 return baseline + voffset;
6990 public int getBaseline() {
6991 if (mLayout == null) {
6992 return super.getBaseline();
6995 return getBaselineOffset() + mLayout.getLineBaseline(0);
6998 int getBaselineOffset() {
7000 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7001 voffset = getVerticalOffset(true);
7004 if (isLayoutModeOptical(mParent)) {
7005 voffset -= getOpticalInsets().top;
7008 return getExtendedPaddingTop() + voffset;
7015 protected int getFadeTop(boolean offsetRequired) {
7016 if (mLayout == null) return 0;
7019 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7020 voffset = getVerticalOffset(true);
7023 if (offsetRequired) voffset += getTopPaddingOffset();
7025 return getExtendedPaddingTop() + voffset;
7032 protected int getFadeHeight(boolean offsetRequired) {
7033 return mLayout != null ? mLayout.getHeight() : 0;
7037 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
7038 if (mText instanceof Spannable && mLinksClickable) {
7039 final float x = event.getX(pointerIndex);
7040 final float y = event.getY(pointerIndex);
7041 final int offset = getOffsetForPosition(x, y);
7042 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
7043 ClickableSpan.class);
7044 if (clickables.length > 0) {
7045 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
7048 if (isTextSelectable() || isTextEditable()) {
7049 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
7051 return super.onResolvePointerIcon(event, pointerIndex);
7055 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
7056 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
7057 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
7058 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
7059 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
7062 return super.onKeyPreIme(keyCode, event);
7068 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
7069 // Do nothing unless mEditor is in text action mode.
7070 if (mEditor == null || mEditor.getTextActionMode() == null) {
7074 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
7075 KeyEvent.DispatcherState state = getKeyDispatcherState();
7076 if (state != null) {
7077 state.startTracking(event, this);
7080 } else if (event.getAction() == KeyEvent.ACTION_UP) {
7081 KeyEvent.DispatcherState state = getKeyDispatcherState();
7082 if (state != null) {
7083 state.handleUpEvent(event);
7085 if (event.isTracking() && !event.isCanceled()) {
7086 stopTextActionMode();
7094 public boolean onKeyDown(int keyCode, KeyEvent event) {
7095 final int which = doKeyDown(keyCode, event, null);
7096 if (which == KEY_EVENT_NOT_HANDLED) {
7097 return super.onKeyDown(keyCode, event);
7104 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
7105 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
7106 final int which = doKeyDown(keyCode, down, event);
7107 if (which == KEY_EVENT_NOT_HANDLED) {
7108 // Go through default dispatching.
7109 return super.onKeyMultiple(keyCode, repeatCount, event);
7111 if (which == KEY_EVENT_HANDLED) {
7112 // Consumed the whole thing.
7118 // We are going to dispatch the remaining events to either the input
7119 // or movement method. To do this, we will just send a repeated stream
7120 // of down and up events until we have done the complete repeatCount.
7121 // It would be nice if those interfaces had an onKeyMultiple() method,
7122 // but adding that is a more complicated change.
7123 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
7124 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
7125 // mEditor and mEditor.mInput are not null from doKeyDown
7126 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7127 while (--repeatCount > 0) {
7128 mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down);
7129 mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up);
7131 hideErrorIfUnchanged();
7133 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
7134 // mMovement is not null from doKeyDown
7135 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7136 while (--repeatCount > 0) {
7137 mMovement.onKeyDown(this, (Spannable) mText, keyCode, down);
7138 mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
7146 * Returns true if pressing ENTER in this field advances focus instead
7147 * of inserting the character. This is true mostly in single-line fields,
7148 * but also in mail addresses and subjects which will display on multiple
7149 * lines but where it doesn't make sense to insert newlines.
7151 private boolean shouldAdvanceFocusOnEnter() {
7152 if (getKeyListener() == null) {
7161 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7162 == EditorInfo.TYPE_CLASS_TEXT) {
7163 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7164 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
7165 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
7174 * Returns true if pressing TAB in this field advances focus instead
7175 * of inserting the character. Insert tabs only in multi-line editors.
7177 private boolean shouldAdvanceFocusOnTab() {
7178 if (getKeyListener() != null && !mSingleLine && mEditor != null
7179 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
7180 == EditorInfo.TYPE_CLASS_TEXT) {
7181 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
7182 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
7183 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
7190 private boolean isDirectionalNavigationKey(int keyCode) {
7192 case KeyEvent.KEYCODE_DPAD_UP:
7193 case KeyEvent.KEYCODE_DPAD_DOWN:
7194 case KeyEvent.KEYCODE_DPAD_LEFT:
7195 case KeyEvent.KEYCODE_DPAD_RIGHT:
7201 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
7203 return KEY_EVENT_NOT_HANDLED;
7206 // If this is the initial keydown, we don't want to prevent a movement away from this view.
7207 // While this shouldn't be necessary because any time we're preventing default movement we
7208 // should be restricting the focus to remain within this view, thus we'll also receive
7209 // the key up event, occasionally key up events will get dropped and we don't want to
7210 // prevent the user from traversing out of this on the next key down.
7211 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7212 mPreventDefaultMovement = false;
7216 case KeyEvent.KEYCODE_ENTER:
7217 if (event.hasNoModifiers()) {
7218 // When mInputContentType is set, we know that we are
7219 // running in a "modern" cupcake environment, so don't need
7220 // to worry about the application trying to capture
7221 // enter key events.
7222 if (mEditor != null && mEditor.mInputContentType != null) {
7223 // If there is an action listener, given them a
7224 // chance to consume the event.
7225 if (mEditor.mInputContentType.onEditorActionListener != null
7226 && mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7227 this, EditorInfo.IME_NULL, event)) {
7228 mEditor.mInputContentType.enterDown = true;
7229 // We are consuming the enter key for them.
7230 return KEY_EVENT_HANDLED;
7234 // If our editor should move focus when enter is pressed, or
7235 // this is a generated event from an IME action button, then
7236 // don't let it be inserted into the text.
7237 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7238 || shouldAdvanceFocusOnEnter()) {
7239 if (hasOnClickListeners()) {
7240 return KEY_EVENT_NOT_HANDLED;
7242 return KEY_EVENT_HANDLED;
7247 case KeyEvent.KEYCODE_DPAD_CENTER:
7248 if (event.hasNoModifiers()) {
7249 if (shouldAdvanceFocusOnEnter()) {
7250 return KEY_EVENT_NOT_HANDLED;
7255 case KeyEvent.KEYCODE_TAB:
7256 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
7257 if (shouldAdvanceFocusOnTab()) {
7258 return KEY_EVENT_NOT_HANDLED;
7263 // Has to be done on key down (and not on key up) to correctly be intercepted.
7264 case KeyEvent.KEYCODE_BACK:
7265 if (mEditor != null && mEditor.getTextActionMode() != null) {
7266 stopTextActionMode();
7267 return KEY_EVENT_HANDLED;
7271 case KeyEvent.KEYCODE_CUT:
7272 if (event.hasNoModifiers() && canCut()) {
7273 if (onTextContextMenuItem(ID_CUT)) {
7274 return KEY_EVENT_HANDLED;
7279 case KeyEvent.KEYCODE_COPY:
7280 if (event.hasNoModifiers() && canCopy()) {
7281 if (onTextContextMenuItem(ID_COPY)) {
7282 return KEY_EVENT_HANDLED;
7287 case KeyEvent.KEYCODE_PASTE:
7288 if (event.hasNoModifiers() && canPaste()) {
7289 if (onTextContextMenuItem(ID_PASTE)) {
7290 return KEY_EVENT_HANDLED;
7296 if (mEditor != null && mEditor.mKeyListener != null) {
7297 boolean doDown = true;
7298 if (otherEvent != null) {
7301 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
7303 hideErrorIfUnchanged();
7306 return KEY_EVENT_HANDLED;
7308 } catch (AbstractMethodError e) {
7309 // onKeyOther was added after 1.0, so if it isn't
7310 // implemented we need to try to dispatch as a regular down.
7318 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
7321 hideErrorIfUnchanged();
7322 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
7326 // bug 650865: sometimes we get a key event before a layout.
7327 // don't try to move around if we don't know the layout.
7329 if (mMovement != null && mLayout != null) {
7330 boolean doDown = true;
7331 if (otherEvent != null) {
7333 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
7337 return KEY_EVENT_HANDLED;
7339 } catch (AbstractMethodError e) {
7340 // onKeyOther was added after 1.0, so if it isn't
7341 // implemented we need to try to dispatch as a regular down.
7345 if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) {
7346 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
7347 mPreventDefaultMovement = true;
7349 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
7352 // Consume arrows from keyboard devices to prevent focus leaving the editor.
7353 // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those
7354 // to move focus with arrows.
7355 if (event.getSource() == InputDevice.SOURCE_KEYBOARD
7356 && isDirectionalNavigationKey(keyCode)) {
7357 return KEY_EVENT_HANDLED;
7361 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode)
7362 ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
7366 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
7370 public void resetErrorChangedFlag() {
7372 * Keep track of what the error was before doing the input
7373 * so that if an input filter changed the error, we leave
7374 * that error showing. Otherwise, we take down whatever
7375 * error was showing when the user types something.
7377 if (mEditor != null) mEditor.mErrorWasChanged = false;
7383 public void hideErrorIfUnchanged() {
7384 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
7385 setError(null, null);
7390 public boolean onKeyUp(int keyCode, KeyEvent event) {
7392 return super.onKeyUp(keyCode, event);
7395 if (!KeyEvent.isModifierKey(keyCode)) {
7396 mPreventDefaultMovement = false;
7400 case KeyEvent.KEYCODE_DPAD_CENTER:
7401 if (event.hasNoModifiers()) {
7403 * If there is a click listener, just call through to
7404 * super, which will invoke it.
7406 * If there isn't a click listener, try to show the soft
7407 * input method. (It will also
7408 * call performClick(), but that won't do anything in
7411 if (!hasOnClickListeners()) {
7412 if (mMovement != null && mText instanceof Editable
7413 && mLayout != null && onCheckIsTextEditor()) {
7414 InputMethodManager imm = InputMethodManager.peekInstance();
7416 if (imm != null && getShowSoftInputOnFocus()) {
7417 imm.showSoftInput(this, 0);
7422 return super.onKeyUp(keyCode, event);
7424 case KeyEvent.KEYCODE_ENTER:
7425 if (event.hasNoModifiers()) {
7426 if (mEditor != null && mEditor.mInputContentType != null
7427 && mEditor.mInputContentType.onEditorActionListener != null
7428 && mEditor.mInputContentType.enterDown) {
7429 mEditor.mInputContentType.enterDown = false;
7430 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
7431 this, EditorInfo.IME_NULL, event)) {
7436 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
7437 || shouldAdvanceFocusOnEnter()) {
7439 * If there is a click listener, just call through to
7440 * super, which will invoke it.
7442 * If there isn't a click listener, try to advance focus,
7443 * but still call through to super, which will reset the
7444 * pressed state and longpress state. (It will also
7445 * call performClick(), but that won't do anything in
7448 if (!hasOnClickListeners()) {
7449 View v = focusSearch(FOCUS_DOWN);
7452 if (!v.requestFocus(FOCUS_DOWN)) {
7453 throw new IllegalStateException("focus search returned a view "
7454 + "that wasn't able to take focus!");
7458 * Return true because we handled the key; super
7459 * will return false because there was no click
7462 super.onKeyUp(keyCode, event);
7464 } else if ((event.getFlags()
7465 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
7466 // No target for next focus, but make sure the IME
7467 // if this came from it.
7468 InputMethodManager imm = InputMethodManager.peekInstance();
7469 if (imm != null && imm.isActive(this)) {
7470 imm.hideSoftInputFromWindow(getWindowToken(), 0);
7475 return super.onKeyUp(keyCode, event);
7480 if (mEditor != null && mEditor.mKeyListener != null) {
7481 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) {
7486 if (mMovement != null && mLayout != null) {
7487 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
7492 return super.onKeyUp(keyCode, event);
7496 public boolean onCheckIsTextEditor() {
7497 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
7501 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
7502 if (onCheckIsTextEditor() && isEnabled()) {
7503 mEditor.createInputMethodStateIfNeeded();
7504 outAttrs.inputType = getInputType();
7505 if (mEditor.mInputContentType != null) {
7506 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
7507 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
7508 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
7509 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
7510 outAttrs.extras = mEditor.mInputContentType.extras;
7511 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
7513 outAttrs.imeOptions = EditorInfo.IME_NULL;
7514 outAttrs.hintLocales = null;
7516 if (focusSearch(FOCUS_DOWN) != null) {
7517 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
7519 if (focusSearch(FOCUS_UP) != null) {
7520 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
7522 if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
7523 == EditorInfo.IME_ACTION_UNSPECIFIED) {
7524 if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
7525 // An action has not been set, but the enter key will move to
7526 // the next focus, so set the action to that.
7527 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
7529 // An action has not been set, and there is no focus to move
7530 // to, so let's just supply a "done" action.
7531 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
7533 if (!shouldAdvanceFocusOnEnter()) {
7534 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7537 if (isMultilineInputType(outAttrs.inputType)) {
7538 // Multi-line text editors should always show an enter key.
7539 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
7541 outAttrs.hintText = mHint;
7542 if (mText instanceof Editable) {
7543 InputConnection ic = new EditableInputConnection(this);
7544 outAttrs.initialSelStart = getSelectionStart();
7545 outAttrs.initialSelEnd = getSelectionEnd();
7546 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
7554 * If this TextView contains editable content, extract a portion of it
7555 * based on the information in <var>request</var> in to <var>outText</var>.
7556 * @return Returns true if the text was successfully extracted, else false.
7558 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
7559 createEditorIfNeeded();
7560 return mEditor.extractText(request, outText);
7564 * This is used to remove all style-impacting spans from text before new
7565 * extracted text is being replaced into it, so that we don't have any
7566 * lingering spans applied during the replace.
7568 static void removeParcelableSpans(Spannable spannable, int start, int end) {
7569 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
7570 int i = spans.length;
7573 spannable.removeSpan(spans[i]);
7578 * Apply to this text view the given extracted text, as previously
7579 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
7581 public void setExtractedText(ExtractedText text) {
7582 Editable content = getEditableText();
7583 if (text.text != null) {
7584 if (content == null) {
7585 setText(text.text, TextView.BufferType.EDITABLE);
7588 int end = content.length();
7590 if (text.partialStartOffset >= 0) {
7591 final int N = content.length();
7592 start = text.partialStartOffset;
7593 if (start > N) start = N;
7594 end = text.partialEndOffset;
7595 if (end > N) end = N;
7598 removeParcelableSpans(content, start, end);
7599 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
7600 if (text.text instanceof Spanned) {
7601 // OK to copy spans only.
7602 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
7603 Object.class, content, start);
7606 content.replace(start, end, text.text);
7611 // Now set the selection position... make sure it is in range, to
7612 // avoid crashes. If this is a partial update, it is possible that
7613 // the underlying text may have changed, causing us problems here.
7614 // Also we just don't want to trust clients to do the right thing.
7615 Spannable sp = (Spannable) getText();
7616 final int N = sp.length();
7617 int start = text.selectionStart;
7620 } else if (start > N) {
7623 int end = text.selectionEnd;
7626 } else if (end > N) {
7629 Selection.setSelection(sp, start, end);
7631 // Finally, update the selection mode.
7632 if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) {
7633 MetaKeyKeyListener.startSelecting(this, sp);
7635 MetaKeyKeyListener.stopSelecting(this, sp);
7642 public void setExtracting(ExtractedTextRequest req) {
7643 if (mEditor.mInputMethodState != null) {
7644 mEditor.mInputMethodState.mExtractedTextRequest = req;
7646 // This would stop a possible selection mode, but no such mode is started in case
7647 // extracted mode will start. Some text is selected though, and will trigger an action mode
7648 // in the extracted view.
7649 mEditor.hideCursorAndSpanControllers();
7650 stopTextActionMode();
7651 if (mEditor.mSelectionModifierCursorController != null) {
7652 mEditor.mSelectionModifierCursorController.resetTouchOffsets();
7657 * Called by the framework in response to a text completion from
7658 * the current input method, provided by it calling
7659 * {@link InputConnection#commitCompletion
7660 * InputConnection.commitCompletion()}. The default implementation does
7661 * nothing; text views that are supporting auto-completion should override
7662 * this to do their desired behavior.
7664 * @param text The auto complete text the user has selected.
7666 public void onCommitCompletion(CompletionInfo text) {
7667 // intentionally empty
7671 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
7672 * dictionary) from the current input method, provided by it calling
7673 * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}.
7674 * The default implementation flashes the background of the corrected word to provide
7675 * feedback to the user.
7677 * @param info The auto correct info about the text that was corrected.
7679 public void onCommitCorrection(CorrectionInfo info) {
7680 if (mEditor != null) mEditor.onCommitCorrection(info);
7683 public void beginBatchEdit() {
7684 if (mEditor != null) mEditor.beginBatchEdit();
7687 public void endBatchEdit() {
7688 if (mEditor != null) mEditor.endBatchEdit();
7692 * Called by the framework in response to a request to begin a batch
7693 * of edit operations through a call to link {@link #beginBatchEdit()}.
7695 public void onBeginBatchEdit() {
7696 // intentionally empty
7700 * Called by the framework in response to a request to end a batch
7701 * of edit operations through a call to link {@link #endBatchEdit}.
7703 public void onEndBatchEdit() {
7704 // intentionally empty
7708 * Called by the framework in response to a private command from the
7709 * current method, provided by it calling
7710 * {@link InputConnection#performPrivateCommand
7711 * InputConnection.performPrivateCommand()}.
7713 * @param action The action name of the command.
7714 * @param data Any additional data for the command. This may be null.
7715 * @return Return true if you handled the command, else false.
7717 public boolean onPrivateIMECommand(String action, Bundle data) {
7721 private void nullLayouts() {
7722 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
7723 mSavedLayout = (BoringLayout) mLayout;
7725 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
7726 mSavedHintLayout = (BoringLayout) mHintLayout;
7729 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
7731 mBoring = mHintBoring = null;
7733 // Since it depends on the value of mLayout
7734 if (mEditor != null) mEditor.prepareCursorControllers();
7738 * Make a new Layout based on the already-measured size of the view,
7739 * on the assumption that it was measured correctly at some point.
7741 private void assumeLayout() {
7742 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7748 int physicalWidth = width;
7750 if (mHorizontallyScrolling) {
7754 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
7755 physicalWidth, false);
7758 private Layout.Alignment getLayoutAlignment() {
7759 Layout.Alignment alignment;
7760 switch (getTextAlignment()) {
7761 case TEXT_ALIGNMENT_GRAVITY:
7762 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
7764 alignment = Layout.Alignment.ALIGN_NORMAL;
7767 alignment = Layout.Alignment.ALIGN_OPPOSITE;
7770 alignment = Layout.Alignment.ALIGN_LEFT;
7773 alignment = Layout.Alignment.ALIGN_RIGHT;
7775 case Gravity.CENTER_HORIZONTAL:
7776 alignment = Layout.Alignment.ALIGN_CENTER;
7779 alignment = Layout.Alignment.ALIGN_NORMAL;
7783 case TEXT_ALIGNMENT_TEXT_START:
7784 alignment = Layout.Alignment.ALIGN_NORMAL;
7786 case TEXT_ALIGNMENT_TEXT_END:
7787 alignment = Layout.Alignment.ALIGN_OPPOSITE;
7789 case TEXT_ALIGNMENT_CENTER:
7790 alignment = Layout.Alignment.ALIGN_CENTER;
7792 case TEXT_ALIGNMENT_VIEW_START:
7793 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7794 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
7796 case TEXT_ALIGNMENT_VIEW_END:
7797 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL)
7798 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
7800 case TEXT_ALIGNMENT_INHERIT:
7801 // This should never happen as we have already resolved the text alignment
7802 // but better safe than sorry so we just fall through
7804 alignment = Layout.Alignment.ALIGN_NORMAL;
7811 * The width passed in is now the desired layout width,
7812 * not the full view width with padding.
7815 protected void makeNewLayout(int wantWidth, int hintWidth,
7816 BoringLayout.Metrics boring,
7817 BoringLayout.Metrics hintBoring,
7818 int ellipsisWidth, boolean bringIntoView) {
7821 // Update "old" cached values
7822 mOldMaximum = mMaximum;
7823 mOldMaxMode = mMaxMode;
7825 mHighlightPathBogus = true;
7827 if (wantWidth < 0) {
7830 if (hintWidth < 0) {
7834 Layout.Alignment alignment = getLayoutAlignment();
7835 final boolean testDirChange = mSingleLine && mLayout != null
7836 && (alignment == Layout.Alignment.ALIGN_NORMAL
7837 || alignment == Layout.Alignment.ALIGN_OPPOSITE);
7839 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
7840 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
7841 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE
7842 && mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
7843 TruncateAt effectiveEllipsize = mEllipsize;
7844 if (mEllipsize == TruncateAt.MARQUEE
7845 && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7846 effectiveEllipsize = TruncateAt.END_SMALL;
7849 if (mTextDir == null) {
7850 mTextDir = getTextDirectionHeuristic();
7853 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
7854 effectiveEllipsize, effectiveEllipsize == mEllipsize);
7855 if (switchEllipsize) {
7856 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE
7857 ? TruncateAt.END : TruncateAt.MARQUEE;
7858 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
7859 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
7862 shouldEllipsize = mEllipsize != null;
7865 if (mHint != null) {
7866 if (shouldEllipsize) hintWidth = wantWidth;
7868 if (hintBoring == UNKNOWN_BORING) {
7869 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
7871 if (hintBoring != null) {
7872 mHintBoring = hintBoring;
7876 if (hintBoring != null) {
7877 if (hintBoring.width <= hintWidth
7878 && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
7879 if (mSavedHintLayout != null) {
7880 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7881 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7882 hintBoring, mIncludePad);
7884 mHintLayout = BoringLayout.make(mHint, mTextPaint,
7885 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7886 hintBoring, mIncludePad);
7889 mSavedHintLayout = (BoringLayout) mHintLayout;
7890 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
7891 if (mSavedHintLayout != null) {
7892 mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint,
7893 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7894 hintBoring, mIncludePad, mEllipsize,
7897 mHintLayout = BoringLayout.make(mHint, mTextPaint,
7898 hintWidth, alignment, mSpacingMult, mSpacingAdd,
7899 hintBoring, mIncludePad, mEllipsize,
7904 // TODO: code duplication with makeSingleLayout()
7905 if (mHintLayout == null) {
7906 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
7907 mHint.length(), mTextPaint, hintWidth)
7908 .setAlignment(alignment)
7909 .setTextDirection(mTextDir)
7910 .setLineSpacing(mSpacingAdd, mSpacingMult)
7911 .setIncludePad(mIncludePad)
7912 .setBreakStrategy(mBreakStrategy)
7913 .setHyphenationFrequency(mHyphenationFrequency)
7914 .setJustificationMode(mJustificationMode)
7915 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
7916 if (shouldEllipsize) {
7917 builder.setEllipsize(mEllipsize)
7918 .setEllipsizedWidth(ellipsisWidth);
7920 mHintLayout = builder.build();
7924 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
7925 registerForPreDraw();
7928 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7929 if (!compressText(ellipsisWidth)) {
7930 final int height = mLayoutParams.height;
7931 // If the size of the view does not depend on the size of the text, try to
7932 // start the marquee immediately
7933 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
7936 // Defer the start of the marquee until we know our width (see setFrame())
7937 mRestartMarquee = true;
7942 // CursorControllers need a non-null mLayout
7943 if (mEditor != null) mEditor.prepareCursorControllers();
7949 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
7950 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
7952 Layout result = null;
7953 if (mText instanceof Spannable) {
7954 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
7955 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
7956 mBreakStrategy, mHyphenationFrequency, mJustificationMode,
7957 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
7959 if (boring == UNKNOWN_BORING) {
7960 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7961 if (boring != null) {
7966 if (boring != null) {
7967 if (boring.width <= wantWidth
7968 && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
7969 if (useSaved && mSavedLayout != null) {
7970 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7971 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7972 boring, mIncludePad);
7974 result = BoringLayout.make(mTransformed, mTextPaint,
7975 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7976 boring, mIncludePad);
7980 mSavedLayout = (BoringLayout) result;
7982 } else if (shouldEllipsize && boring.width <= wantWidth) {
7983 if (useSaved && mSavedLayout != null) {
7984 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
7985 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7986 boring, mIncludePad, effectiveEllipsize,
7989 result = BoringLayout.make(mTransformed, mTextPaint,
7990 wantWidth, alignment, mSpacingMult, mSpacingAdd,
7991 boring, mIncludePad, effectiveEllipsize,
7997 if (result == null) {
7998 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
7999 0, mTransformed.length(), mTextPaint, wantWidth)
8000 .setAlignment(alignment)
8001 .setTextDirection(mTextDir)
8002 .setLineSpacing(mSpacingAdd, mSpacingMult)
8003 .setIncludePad(mIncludePad)
8004 .setBreakStrategy(mBreakStrategy)
8005 .setHyphenationFrequency(mHyphenationFrequency)
8006 .setJustificationMode(mJustificationMode)
8007 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
8008 if (shouldEllipsize) {
8009 builder.setEllipsize(effectiveEllipsize)
8010 .setEllipsizedWidth(ellipsisWidth);
8012 // TODO: explore always setting maxLines
8013 result = builder.build();
8018 private boolean compressText(float width) {
8019 if (isHardwareAccelerated()) return false;
8021 // Only compress the text if it hasn't been compressed by the previous pass
8022 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX
8023 && mTextPaint.getTextScaleX() == 1.0f) {
8024 final float textWidth = mLayout.getLineWidth(0);
8025 final float overflow = (textWidth + 1.0f - width) / width;
8026 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
8027 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
8028 post(new Runnable() {
8040 private static int desired(Layout layout) {
8041 int n = layout.getLineCount();
8042 CharSequence text = layout.getText();
8045 // if any line was wrapped, we can't use it.
8046 // but it's ok for the last line not to have a newline
8048 for (int i = 0; i < n - 1; i++) {
8049 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') {
8054 for (int i = 0; i < n; i++) {
8055 max = Math.max(max, layout.getLineWidth(i));
8058 return (int) Math.ceil(max);
8062 * Set whether the TextView includes extra top and bottom padding to make
8063 * room for accents that go above the normal ascent and descent.
8064 * The default is true.
8066 * @see #getIncludeFontPadding()
8068 * @attr ref android.R.styleable#TextView_includeFontPadding
8070 public void setIncludeFontPadding(boolean includepad) {
8071 if (mIncludePad != includepad) {
8072 mIncludePad = includepad;
8074 if (mLayout != null) {
8083 * Gets whether the TextView includes extra top and bottom padding to make
8084 * room for accents that go above the normal ascent and descent.
8086 * @see #setIncludeFontPadding(boolean)
8088 * @attr ref android.R.styleable#TextView_includeFontPadding
8090 public boolean getIncludeFontPadding() {
8094 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
8097 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
8098 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
8099 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8100 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
8101 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
8106 BoringLayout.Metrics boring = UNKNOWN_BORING;
8107 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
8109 if (mTextDir == null) {
8110 mTextDir = getTextDirectionHeuristic();
8114 boolean fromexisting = false;
8116 if (widthMode == MeasureSpec.EXACTLY) {
8117 // Parent has told us how big to be. So be it.
8120 if (mLayout != null && mEllipsize == null) {
8121 des = desired(mLayout);
8125 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
8126 if (boring != null) {
8130 fromexisting = true;
8133 if (boring == null || boring == UNKNOWN_BORING) {
8135 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0,
8136 mTransformed.length(), mTextPaint, mTextDir));
8140 width = boring.width;
8143 final Drawables dr = mDrawables;
8145 width = Math.max(width, dr.mDrawableWidthTop);
8146 width = Math.max(width, dr.mDrawableWidthBottom);
8149 if (mHint != null) {
8153 if (mHintLayout != null && mEllipsize == null) {
8154 hintDes = desired(mHintLayout);
8158 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
8159 if (hintBoring != null) {
8160 mHintBoring = hintBoring;
8164 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
8166 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, 0, mHint.length(),
8167 mTextPaint, mTextDir));
8169 hintWidth = hintDes;
8171 hintWidth = hintBoring.width;
8174 if (hintWidth > width) {
8179 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
8181 if (mMaxWidthMode == EMS) {
8182 width = Math.min(width, mMaxWidth * getLineHeight());
8184 width = Math.min(width, mMaxWidth);
8187 if (mMinWidthMode == EMS) {
8188 width = Math.max(width, mMinWidth * getLineHeight());
8190 width = Math.max(width, mMinWidth);
8193 // Check against our minimum width
8194 width = Math.max(width, getSuggestedMinimumWidth());
8196 if (widthMode == MeasureSpec.AT_MOST) {
8197 width = Math.min(widthSize, width);
8201 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
8202 int unpaddedWidth = want;
8204 if (mHorizontallyScrolling) want = VERY_WIDE;
8206 int hintWant = want;
8207 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
8209 if (mLayout == null) {
8210 makeNewLayout(want, hintWant, boring, hintBoring,
8211 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8213 final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
8214 || (mLayout.getEllipsizedWidth()
8215 != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
8217 final boolean widthChanged = (mHint == null) && (mEllipsize == null)
8218 && (want > mLayout.getWidth())
8219 && (mLayout instanceof BoringLayout
8220 || (fromexisting && des >= 0 && des <= want));
8222 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
8224 if (layoutChanged || maximumChanged) {
8225 if (!maximumChanged && widthChanged) {
8226 mLayout.increaseWidthTo(want);
8228 makeNewLayout(want, hintWant, boring, hintBoring,
8229 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
8232 // Nothing has changed
8236 if (heightMode == MeasureSpec.EXACTLY) {
8237 // Parent has told us how big to be. So be it.
8238 height = heightSize;
8239 mDesiredHeightAtMeasure = -1;
8241 int desired = getDesiredHeight();
8244 mDesiredHeightAtMeasure = desired;
8246 if (heightMode == MeasureSpec.AT_MOST) {
8247 height = Math.min(desired, heightSize);
8251 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
8252 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
8253 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
8257 * We didn't let makeNewLayout() register to bring the cursor into view,
8258 * so do it here if there is any possibility that it is needed.
8260 if (mMovement != null
8261 || mLayout.getWidth() > unpaddedWidth
8262 || mLayout.getHeight() > unpaddedHeight) {
8263 registerForPreDraw();
8268 setMeasuredDimension(width, height);
8272 * Automatically computes and sets the text size.
8274 private void autoSizeText() {
8275 if (!isAutoSizeEnabled()) {
8279 if (mNeedsAutoSizeText) {
8280 if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) {
8284 final int availableWidth = mHorizontallyScrolling
8286 : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight();
8287 final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom()
8288 - getExtendedPaddingTop();
8290 if (availableWidth <= 0 || availableHeight <= 0) {
8294 synchronized (TEMP_RECTF) {
8295 TEMP_RECTF.setEmpty();
8296 TEMP_RECTF.right = availableWidth;
8297 TEMP_RECTF.bottom = availableHeight;
8298 final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF);
8300 if (optimalTextSize != getTextSize()) {
8301 setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize,
8302 false /* shouldRequestLayout */);
8304 makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING,
8305 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8306 false /* bringIntoView */);
8310 // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing
8311 // after the next layout pass should set this to false.
8312 mNeedsAutoSizeText = true;
8316 * Performs a binary search to find the largest text size that will still fit within the size
8317 * available to this view.
8319 private int findLargestTextSizeWhichFits(RectF availableSpace) {
8320 final int sizesCount = mAutoSizeTextSizesInPx.length;
8321 if (sizesCount == 0) {
8322 throw new IllegalStateException("No available text sizes to choose from.");
8325 int bestSizeIndex = 0;
8326 int lowIndex = bestSizeIndex + 1;
8327 int highIndex = sizesCount - 1;
8329 while (lowIndex <= highIndex) {
8330 sizeToTryIndex = (lowIndex + highIndex) / 2;
8331 if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
8332 bestSizeIndex = lowIndex;
8333 lowIndex = sizeToTryIndex + 1;
8335 highIndex = sizeToTryIndex - 1;
8336 bestSizeIndex = highIndex;
8340 return mAutoSizeTextSizesInPx[bestSizeIndex];
8343 private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
8344 final CharSequence text = mTransformed != null
8347 final int maxLines = getMaxLines();
8348 if (mTempTextPaint == null) {
8349 mTempTextPaint = new TextPaint();
8351 mTempTextPaint.reset();
8353 mTempTextPaint.set(getPaint());
8354 mTempTextPaint.setTextSize(suggestedSizeInPx);
8356 final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
8357 text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right));
8359 layoutBuilder.setAlignment(getLayoutAlignment())
8360 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
8361 .setIncludePad(getIncludeFontPadding())
8362 .setBreakStrategy(getBreakStrategy())
8363 .setHyphenationFrequency(getHyphenationFrequency())
8364 .setJustificationMode(getJustificationMode())
8365 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
8366 .setTextDirection(getTextDirectionHeuristic());
8368 final StaticLayout layout = layoutBuilder.build();
8371 if (maxLines != -1 && layout.getLineCount() > maxLines) {
8376 if (layout.getHeight() > availableSpace.bottom) {
8383 private int getDesiredHeight() {
8385 getDesiredHeight(mLayout, true),
8386 getDesiredHeight(mHintLayout, mEllipsize != null));
8389 private int getDesiredHeight(Layout layout, boolean cap) {
8390 if (layout == null) {
8395 * Don't cap the hint to a certain number of lines.
8396 * (Do cap it, though, if we have a maximum pixel height.)
8398 int desired = layout.getHeight(cap);
8400 final Drawables dr = mDrawables;
8402 desired = Math.max(desired, dr.mDrawableHeightLeft);
8403 desired = Math.max(desired, dr.mDrawableHeightRight);
8406 int linecount = layout.getLineCount();
8407 final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
8410 if (mMaxMode != LINES) {
8411 desired = Math.min(desired, mMaximum);
8412 } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
8413 || layout instanceof BoringLayout)) {
8414 desired = layout.getLineTop(mMaximum);
8417 desired = Math.max(desired, dr.mDrawableHeightLeft);
8418 desired = Math.max(desired, dr.mDrawableHeightRight);
8422 linecount = mMaximum;
8425 if (mMinMode == LINES) {
8426 if (linecount < mMinimum) {
8427 desired += getLineHeight() * (mMinimum - linecount);
8430 desired = Math.max(desired, mMinimum);
8433 // Check against our minimum height
8434 desired = Math.max(desired, getSuggestedMinimumHeight());
8440 * Check whether a change to the existing text layout requires a
8443 private void checkForResize() {
8444 boolean sizeChanged = false;
8446 if (mLayout != null) {
8447 // Check if our width changed
8448 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
8453 // Check if our height changed
8454 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
8455 int desiredHeight = getDesiredHeight();
8457 if (desiredHeight != this.getHeight()) {
8460 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
8461 if (mDesiredHeightAtMeasure >= 0) {
8462 int desiredHeight = getDesiredHeight();
8464 if (desiredHeight != mDesiredHeightAtMeasure) {
8473 // caller will have already invalidated
8478 * Check whether entirely new text requires a new view layout
8479 * or merely a new text layout.
8481 private void checkForRelayout() {
8482 // If we have a fixed width, we can just swap in a new text layout
8483 // if the text height stays the same or if the view height is fixed.
8485 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
8486 || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
8487 && (mHint == null || mHintLayout != null)
8488 && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
8489 // Static width, so try making a new text layout.
8491 int oldht = mLayout.getHeight();
8492 int want = mLayout.getWidth();
8493 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
8496 * No need to bring the text into view, since the size is not
8497 * changing (unless we do the requestLayout(), in which case it
8498 * will happen at measure).
8500 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
8501 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
8504 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
8505 // In a fixed-height view, so use our new text layout.
8506 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
8507 && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
8513 // Dynamic height, but height has stayed the same,
8514 // so use our new text layout.
8515 if (mLayout.getHeight() == oldht
8516 && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
8523 // We lose: the height has changed and we have a dynamic height.
8524 // Request a new view layout using our new text layout.
8528 // Dynamic width, so we have no choice but to request a new
8529 // view layout with a new text layout.
8537 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8538 super.onLayout(changed, left, top, right, bottom);
8539 if (mDeferScroll >= 0) {
8540 int curs = mDeferScroll;
8542 bringPointIntoView(Math.min(curs, mText.length()));
8544 // Call auto-size after the width and height have been calculated.
8548 private boolean isShowingHint() {
8549 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
8553 * Returns true if anything changed.
8555 private boolean bringTextIntoView() {
8556 Layout layout = isShowingHint() ? mHintLayout : mLayout;
8558 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8559 line = layout.getLineCount() - 1;
8562 Layout.Alignment a = layout.getParagraphAlignment(line);
8563 int dir = layout.getParagraphDirection(line);
8564 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8565 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8566 int ht = layout.getHeight();
8568 int scrollx, scrolly;
8570 // Convert to left, center, or right alignment.
8571 if (a == Layout.Alignment.ALIGN_NORMAL) {
8572 a = dir == Layout.DIR_LEFT_TO_RIGHT
8573 ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
8574 } else if (a == Layout.Alignment.ALIGN_OPPOSITE) {
8575 a = dir == Layout.DIR_LEFT_TO_RIGHT
8576 ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
8579 if (a == Layout.Alignment.ALIGN_CENTER) {
8581 * Keep centered if possible, or, if it is too wide to fit,
8582 * keep leading edge in view.
8585 int left = (int) Math.floor(layout.getLineLeft(line));
8586 int right = (int) Math.ceil(layout.getLineRight(line));
8588 if (right - left < hspace) {
8589 scrollx = (right + left) / 2 - hspace / 2;
8592 scrollx = right - hspace;
8597 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
8598 int right = (int) Math.ceil(layout.getLineRight(line));
8599 scrollx = right - hspace;
8600 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
8601 scrollx = (int) Math.floor(layout.getLineLeft(line));
8607 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8608 scrolly = ht - vspace;
8614 if (scrollx != mScrollX || scrolly != mScrollY) {
8615 scrollTo(scrollx, scrolly);
8623 * Move the point, specified by the offset, into the view if it is needed.
8624 * This has to be called after layout. Returns true if anything changed.
8626 public boolean bringPointIntoView(int offset) {
8627 if (isLayoutRequested()) {
8628 mDeferScroll = offset;
8631 boolean changed = false;
8633 Layout layout = isShowingHint() ? mHintLayout : mLayout;
8635 if (layout == null) return changed;
8637 int line = layout.getLineForOffset(offset);
8641 switch (layout.getParagraphAlignment(line)) {
8649 grav = layout.getParagraphDirection(line);
8651 case ALIGN_OPPOSITE:
8652 grav = -layout.getParagraphDirection(line);
8660 // We only want to clamp the cursor to fit within the layout width
8661 // in left-to-right modes, because in a right to left alignment,
8662 // we want to scroll to keep the line-right on the screen, as other
8663 // lines are likely to have text flush with the right margin, which
8664 // we want to keep visible.
8665 // A better long-term solution would probably be to measure both
8666 // the full line and a blank-trimmed version, and, for example, use
8667 // the latter measurement for centering and right alignment, but for
8668 // the time being we only implement the cursor clamping in left to
8669 // right where it is most likely to be annoying.
8670 final boolean clamped = grav > 0;
8671 // FIXME: Is it okay to truncate this, or should we round?
8672 final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
8673 final int top = layout.getLineTop(line);
8674 final int bottom = layout.getLineTop(line + 1);
8676 int left = (int) Math.floor(layout.getLineLeft(line));
8677 int right = (int) Math.ceil(layout.getLineRight(line));
8678 int ht = layout.getHeight();
8680 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8681 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8682 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
8683 // If cursor has been clamped, make sure we don't scroll.
8684 right = Math.max(x, left + hspace);
8687 int hslack = (bottom - top) / 2;
8688 int vslack = hslack;
8690 if (vslack > vspace / 4) {
8691 vslack = vspace / 4;
8693 if (hslack > hspace / 4) {
8694 hslack = hspace / 4;
8700 if (top - vs < vslack) {
8703 if (bottom - vs > vspace - vslack) {
8704 vs = bottom - (vspace - vslack);
8706 if (ht - vs < vspace) {
8714 if (x - hs < hslack) {
8717 if (x - hs > hspace - hslack) {
8718 hs = x - (hspace - hslack);
8723 if (left - hs > 0) {
8726 if (right - hs < hspace) {
8727 hs = right - hspace;
8729 } else if (grav > 0) {
8730 if (right - hs < hspace) {
8731 hs = right - hspace;
8733 if (left - hs > 0) {
8736 } else /* grav == 0 */ {
8737 if (right - left <= hspace) {
8739 * If the entire text fits, center it exactly.
8741 hs = left - (hspace - (right - left)) / 2;
8742 } else if (x > right - hslack) {
8744 * If we are near the right edge, keep the right edge
8745 * at the edge of the view.
8747 hs = right - hspace;
8748 } else if (x < left + hslack) {
8750 * If we are near the left edge, keep the left edge
8751 * at the edge of the view.
8754 } else if (left > hs) {
8756 * Is there whitespace visible at the left? Fix it if so.
8759 } else if (right < hs + hspace) {
8761 * Is there whitespace visible at the right? Fix it if so.
8763 hs = right - hspace;
8766 * Otherwise, float as needed.
8768 if (x - hs < hslack) {
8771 if (x - hs > hspace - hslack) {
8772 hs = x - (hspace - hslack);
8777 if (hs != mScrollX || vs != mScrollY) {
8778 if (mScroller == null) {
8781 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
8782 int dx = hs - mScrollX;
8783 int dy = vs - mScrollY;
8785 if (duration > ANIMATED_SCROLL_GAP) {
8786 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
8787 awakenScrollBars(mScroller.getDuration());
8790 if (!mScroller.isFinished()) {
8791 mScroller.abortAnimation();
8797 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
8804 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
8805 // requestRectangleOnScreen() is in terms of content coordinates.
8807 // The offsets here are to ensure the rectangle we are using is
8808 // within our view bounds, in case the cursor is on the far left
8809 // or right. If it isn't withing the bounds, then this request
8811 if (mTempRect == null) mTempRect = new Rect();
8812 mTempRect.set(x - 2, top, x + 2, bottom);
8813 getInterestingRect(mTempRect, line);
8814 mTempRect.offset(mScrollX, mScrollY);
8816 if (requestRectangleOnScreen(mTempRect)) {
8825 * Move the cursor, if needed, so that it is at an offset that is visible
8826 * to the user. This will not move the cursor if it represents more than
8827 * one character (a selection range). This will only work if the
8828 * TextView contains spannable text; otherwise it will do nothing.
8830 * @return True if the cursor was actually moved, false otherwise.
8832 public boolean moveCursorToVisibleOffset() {
8833 if (!(mText instanceof Spannable)) {
8836 int start = getSelectionStart();
8837 int end = getSelectionEnd();
8842 // First: make sure the line is visible on screen:
8844 int line = mLayout.getLineForOffset(start);
8846 final int top = mLayout.getLineTop(line);
8847 final int bottom = mLayout.getLineTop(line + 1);
8848 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
8849 int vslack = (bottom - top) / 2;
8850 if (vslack > vspace / 4) {
8851 vslack = vspace / 4;
8853 final int vs = mScrollY;
8855 if (top < (vs + vslack)) {
8856 line = mLayout.getLineForVertical(vs + vslack + (bottom - top));
8857 } else if (bottom > (vspace + vs - vslack)) {
8858 line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top));
8861 // Next: make sure the character is visible on screen:
8863 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
8864 final int hs = mScrollX;
8865 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
8866 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs);
8868 // line might contain bidirectional text
8869 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
8870 final int highChar = leftChar > rightChar ? leftChar : rightChar;
8872 int newStart = start;
8873 if (newStart < lowChar) {
8875 } else if (newStart > highChar) {
8876 newStart = highChar;
8879 if (newStart != start) {
8880 Selection.setSelection((Spannable) mText, newStart);
8888 public void computeScroll() {
8889 if (mScroller != null) {
8890 if (mScroller.computeScrollOffset()) {
8891 mScrollX = mScroller.getCurrX();
8892 mScrollY = mScroller.getCurrY();
8893 invalidateParentCaches();
8894 postInvalidate(); // So we draw again
8899 private void getInterestingRect(Rect r, int line) {
8900 convertFromViewportToContentCoordinates(r);
8902 // Rectangle can can be expanded on first and last line to take
8903 // padding into account.
8904 // TODO Take left/right padding into account too?
8905 if (line == 0) r.top -= getExtendedPaddingTop();
8906 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
8909 private void convertFromViewportToContentCoordinates(Rect r) {
8910 final int horizontalOffset = viewportToContentHorizontalOffset();
8911 r.left += horizontalOffset;
8912 r.right += horizontalOffset;
8914 final int verticalOffset = viewportToContentVerticalOffset();
8915 r.top += verticalOffset;
8916 r.bottom += verticalOffset;
8919 int viewportToContentHorizontalOffset() {
8920 return getCompoundPaddingLeft() - mScrollX;
8923 int viewportToContentVerticalOffset() {
8924 int offset = getExtendedPaddingTop() - mScrollY;
8925 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
8926 offset += getVerticalOffset(false);
8932 public void debug(int depth) {
8935 String output = debugIndent(depth);
8936 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
8937 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
8940 if (mText != null) {
8942 output += "mText=\"" + mText + "\" ";
8943 if (mLayout != null) {
8944 output += "mLayout width=" + mLayout.getWidth()
8945 + " height=" + mLayout.getHeight();
8948 output += "mText=NULL";
8950 Log.d(VIEW_LOG_TAG, output);
8954 * Convenience for {@link Selection#getSelectionStart}.
8956 @ViewDebug.ExportedProperty(category = "text")
8957 public int getSelectionStart() {
8958 return Selection.getSelectionStart(getText());
8962 * Convenience for {@link Selection#getSelectionEnd}.
8964 @ViewDebug.ExportedProperty(category = "text")
8965 public int getSelectionEnd() {
8966 return Selection.getSelectionEnd(getText());
8970 * Return true iff there is a selection inside this text view.
8972 public boolean hasSelection() {
8973 final int selectionStart = getSelectionStart();
8974 final int selectionEnd = getSelectionEnd();
8976 return selectionStart >= 0 && selectionStart != selectionEnd;
8979 String getSelectedText() {
8980 if (!hasSelection()) {
8984 final int start = getSelectionStart();
8985 final int end = getSelectionEnd();
8986 return String.valueOf(
8987 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
8991 * Sets the properties of this field (lines, horizontally scrolling,
8992 * transformation method) to be for a single-line input.
8994 * @attr ref android.R.styleable#TextView_singleLine
8996 public void setSingleLine() {
8997 setSingleLine(true);
9001 * Sets the properties of this field to transform input to ALL CAPS
9002 * display. This may use a "small caps" formatting if available.
9003 * This setting will be ignored if this field is editable or selectable.
9005 * This call replaces the current transformation method. Disabling this
9006 * will not necessarily restore the previous behavior from before this
9009 * @see #setTransformationMethod(TransformationMethod)
9010 * @attr ref android.R.styleable#TextView_textAllCaps
9012 public void setAllCaps(boolean allCaps) {
9014 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
9016 setTransformationMethod(null);
9021 * If true, sets the properties of this field (number of lines, horizontally scrolling,
9022 * transformation method) to be for a single-line input; if false, restores these to the default
9025 * Note that the default conditions are not necessarily those that were in effect prior this
9026 * method, and you may want to reset these properties to your custom values.
9028 * @attr ref android.R.styleable#TextView_singleLine
9030 @android.view.RemotableViewMethod
9031 public void setSingleLine(boolean singleLine) {
9032 // Could be used, but may break backward compatibility.
9033 // if (mSingleLine == singleLine) return;
9034 setInputTypeSingleLine(singleLine);
9035 applySingleLine(singleLine, true, true);
9039 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
9042 private void setInputTypeSingleLine(boolean singleLine) {
9044 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
9045 == EditorInfo.TYPE_CLASS_TEXT) {
9047 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9049 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
9054 private void applySingleLine(boolean singleLine, boolean applyTransformation,
9055 boolean changeMaxLines) {
9056 mSingleLine = singleLine;
9059 setHorizontallyScrolling(true);
9060 if (applyTransformation) {
9061 setTransformationMethod(SingleLineTransformationMethod.getInstance());
9064 if (changeMaxLines) {
9065 setMaxLines(Integer.MAX_VALUE);
9067 setHorizontallyScrolling(false);
9068 if (applyTransformation) {
9069 setTransformationMethod(null);
9075 * Causes words in the text that are longer than the view's width
9076 * to be ellipsized instead of broken in the middle. You may also
9077 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
9078 * to constrain the text to a single line. Use <code>null</code>
9079 * to turn off ellipsizing.
9081 * If {@link #setMaxLines} has been used to set two or more lines,
9082 * only {@link android.text.TextUtils.TruncateAt#END} and
9083 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
9084 * (other ellipsizing types will not do anything).
9086 * @attr ref android.R.styleable#TextView_ellipsize
9088 public void setEllipsize(TextUtils.TruncateAt where) {
9089 // TruncateAt is an enum. != comparison is ok between these singleton objects.
9090 if (mEllipsize != where) {
9093 if (mLayout != null) {
9102 * Sets how many times to repeat the marquee animation. Only applied if the
9103 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
9105 * @see #getMarqueeRepeatLimit()
9107 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9109 public void setMarqueeRepeatLimit(int marqueeLimit) {
9110 mMarqueeRepeatLimit = marqueeLimit;
9114 * Gets the number of times the marquee animation is repeated. Only meaningful if the
9115 * TextView has marquee enabled.
9117 * @return the number of times the marquee animation is repeated. -1 if the animation
9118 * repeats indefinitely
9120 * @see #setMarqueeRepeatLimit(int)
9122 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
9124 public int getMarqueeRepeatLimit() {
9125 return mMarqueeRepeatLimit;
9129 * Returns where, if anywhere, words that are longer than the view
9130 * is wide should be ellipsized.
9132 @ViewDebug.ExportedProperty
9133 public TextUtils.TruncateAt getEllipsize() {
9138 * Set the TextView so that when it takes focus, all the text is
9141 * @attr ref android.R.styleable#TextView_selectAllOnFocus
9143 @android.view.RemotableViewMethod
9144 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
9145 createEditorIfNeeded();
9146 mEditor.mSelectAllOnFocus = selectAllOnFocus;
9148 if (selectAllOnFocus && !(mText instanceof Spannable)) {
9149 setText(mText, BufferType.SPANNABLE);
9154 * Set whether the cursor is visible. The default is true. Note that this property only
9155 * makes sense for editable TextView.
9157 * @see #isCursorVisible()
9159 * @attr ref android.R.styleable#TextView_cursorVisible
9161 @android.view.RemotableViewMethod
9162 public void setCursorVisible(boolean visible) {
9163 if (visible && mEditor == null) return; // visible is the default value with no edit data
9164 createEditorIfNeeded();
9165 if (mEditor.mCursorVisible != visible) {
9166 mEditor.mCursorVisible = visible;
9169 mEditor.makeBlink();
9171 // InsertionPointCursorController depends on mCursorVisible
9172 mEditor.prepareCursorControllers();
9177 * @return whether or not the cursor is visible (assuming this TextView is editable)
9179 * @see #setCursorVisible(boolean)
9181 * @attr ref android.R.styleable#TextView_cursorVisible
9183 public boolean isCursorVisible() {
9184 // true is the default value
9185 return mEditor == null ? true : mEditor.mCursorVisible;
9188 private boolean canMarquee() {
9189 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
9190 return width > 0 && (mLayout.getLineWidth(0) > width
9191 || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null
9192 && mSavedMarqueeModeLayout.getLineWidth(0) > width));
9195 private void startMarquee() {
9196 // Do not ellipsize EditText
9197 if (getKeyListener() != null) return;
9199 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
9203 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected())
9204 && getLineCount() == 1 && canMarquee()) {
9206 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
9207 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
9208 final Layout tmp = mLayout;
9209 mLayout = mSavedMarqueeModeLayout;
9210 mSavedMarqueeModeLayout = tmp;
9211 setHorizontalFadingEdgeEnabled(true);
9216 if (mMarquee == null) mMarquee = new Marquee(this);
9217 mMarquee.start(mMarqueeRepeatLimit);
9221 private void stopMarquee() {
9222 if (mMarquee != null && !mMarquee.isStopped()) {
9226 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
9227 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9228 final Layout tmp = mSavedMarqueeModeLayout;
9229 mSavedMarqueeModeLayout = mLayout;
9231 setHorizontalFadingEdgeEnabled(false);
9237 private void startStopMarquee(boolean start) {
9238 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9248 * This method is called when the text is changed, in case any subclasses
9249 * would like to know.
9251 * Within <code>text</code>, the <code>lengthAfter</code> characters
9252 * beginning at <code>start</code> have just replaced old text that had
9253 * length <code>lengthBefore</code>. It is an error to attempt to make
9254 * changes to <code>text</code> from this callback.
9256 * @param text The text the TextView is displaying
9257 * @param start The offset of the start of the range of the text that was
9259 * @param lengthBefore The length of the former text that has been replaced
9260 * @param lengthAfter The length of the replacement modified text
9262 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
9263 // intentionally empty, template pattern method can be overridden by subclasses
9267 * This method is called when the selection has changed, in case any
9268 * subclasses would like to know.
9270 * @param selStart The new selection start location.
9271 * @param selEnd The new selection end location.
9273 protected void onSelectionChanged(int selStart, int selEnd) {
9274 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
9278 * Adds a TextWatcher to the list of those whose methods are called
9279 * whenever this TextView's text changes.
9281 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
9282 * not called after {@link #setText} calls. Now, doing {@link #setText}
9283 * if there are any text changed listeners forces the buffer type to
9284 * Editable if it would not otherwise be and does call this method.
9286 public void addTextChangedListener(TextWatcher watcher) {
9287 if (mListeners == null) {
9288 mListeners = new ArrayList<TextWatcher>();
9291 mListeners.add(watcher);
9295 * Removes the specified TextWatcher from the list of those whose
9296 * methods are called
9297 * whenever this TextView's text changes.
9299 public void removeTextChangedListener(TextWatcher watcher) {
9300 if (mListeners != null) {
9301 int i = mListeners.indexOf(watcher);
9304 mListeners.remove(i);
9309 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
9310 if (mListeners != null) {
9311 final ArrayList<TextWatcher> list = mListeners;
9312 final int count = list.size();
9313 for (int i = 0; i < count; i++) {
9314 list.get(i).beforeTextChanged(text, start, before, after);
9318 // The spans that are inside or intersect the modified region no longer make sense
9319 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
9320 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
9323 // Removes all spans that are inside or actually overlap the start..end range
9324 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
9325 if (!(mText instanceof Editable)) return;
9326 Editable text = (Editable) mText;
9328 T[] spans = text.getSpans(start, end, type);
9329 final int length = spans.length;
9330 for (int i = 0; i < length; i++) {
9331 final int spanStart = text.getSpanStart(spans[i]);
9332 final int spanEnd = text.getSpanEnd(spans[i]);
9333 if (spanEnd == start || spanStart == end) break;
9334 text.removeSpan(spans[i]);
9338 void removeAdjacentSuggestionSpans(final int pos) {
9339 if (!(mText instanceof Editable)) return;
9340 final Editable text = (Editable) mText;
9342 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
9343 final int length = spans.length;
9344 for (int i = 0; i < length; i++) {
9345 final int spanStart = text.getSpanStart(spans[i]);
9346 final int spanEnd = text.getSpanEnd(spans[i]);
9347 if (spanEnd == pos || spanStart == pos) {
9348 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
9349 text.removeSpan(spans[i]);
9356 * Not private so it can be called from an inner class without going
9359 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
9360 if (mListeners != null) {
9361 final ArrayList<TextWatcher> list = mListeners;
9362 final int count = list.size();
9363 for (int i = 0; i < count; i++) {
9364 list.get(i).onTextChanged(text, start, before, after);
9368 if (mEditor != null) mEditor.sendOnTextChanged(start, before, after);
9372 * Not private so it can be called from an inner class without going
9375 void sendAfterTextChanged(Editable text) {
9376 if (mListeners != null) {
9377 final ArrayList<TextWatcher> list = mListeners;
9378 final int count = list.size();
9379 for (int i = 0; i < count; i++) {
9380 list.get(i).afterTextChanged(text);
9384 // Always notify AutoFillManager - it will return right away if autofill is disabled.
9385 notifyAutoFillManagerAfterTextChangedIfNeeded();
9387 hideErrorIfUnchanged();
9390 private void notifyAutoFillManagerAfterTextChangedIfNeeded() {
9391 // It is important to not check whether the view is important for autofill
9392 // since the user can trigger autofill manually on not important views.
9393 if (!isAutofillable()) {
9396 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
9398 if (DEBUG_AUTOFILL) {
9399 Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
9401 afm.notifyValueChanged(TextView.this);
9405 private boolean isAutofillable() {
9406 // It is important to not check whether the view is important for autofill
9407 // since the user can trigger autofill manually on not important views.
9408 return getAutofillType() != AUTOFILL_TYPE_NONE;
9411 void updateAfterEdit() {
9413 int curs = getSelectionStart();
9415 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
9416 registerForPreDraw();
9422 mHighlightPathBogus = true;
9423 if (mEditor != null) mEditor.makeBlink();
9424 bringPointIntoView(curs);
9429 * Not private so it can be called from an inner class without going
9432 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
9433 sLastCutCopyOrTextChangedTime = 0;
9435 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9436 if (ims == null || ims.mBatchEditNesting == 0) {
9440 ims.mContentChanged = true;
9441 if (ims.mChangedStart < 0) {
9442 ims.mChangedStart = start;
9443 ims.mChangedEnd = start + before;
9445 ims.mChangedStart = Math.min(ims.mChangedStart, start);
9446 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
9448 ims.mChangedDelta += after - before;
9450 resetErrorChangedFlag();
9451 sendOnTextChanged(buffer, start, before, after);
9452 onTextChanged(buffer, start, before, after);
9456 * Not private so it can be called from an inner class without going
9459 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
9460 // XXX Make the start and end move together if this ends up
9461 // spending too much time invalidating.
9463 boolean selChanged = false;
9464 int newSelStart = -1, newSelEnd = -1;
9466 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
9468 if (what == Selection.SELECTION_END) {
9470 newSelEnd = newStart;
9472 if (oldStart >= 0 || newStart >= 0) {
9473 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
9475 registerForPreDraw();
9476 if (mEditor != null) mEditor.makeBlink();
9480 if (what == Selection.SELECTION_START) {
9482 newSelStart = newStart;
9484 if (oldStart >= 0 || newStart >= 0) {
9485 int end = Selection.getSelectionEnd(buf);
9486 invalidateCursor(end, oldStart, newStart);
9491 mHighlightPathBogus = true;
9492 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
9494 if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) {
9495 if (newSelStart < 0) {
9496 newSelStart = Selection.getSelectionStart(buf);
9498 if (newSelEnd < 0) {
9499 newSelEnd = Selection.getSelectionEnd(buf);
9502 if (mEditor != null) {
9503 mEditor.refreshTextActionMode();
9505 && mEditor.getTextActionMode() == null && hasTransientState()) {
9506 // User generated selection has been removed.
9507 setHasTransientState(false);
9510 onSelectionChanged(newSelStart, newSelEnd);
9514 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle
9515 || what instanceof CharacterStyle) {
9516 if (ims == null || ims.mBatchEditNesting == 0) {
9518 mHighlightPathBogus = true;
9521 ims.mContentChanged = true;
9523 if (mEditor != null) {
9524 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
9525 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
9526 mEditor.invalidateHandlesAndActionMode();
9530 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
9531 mHighlightPathBogus = true;
9532 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
9533 ims.mSelectionModeChanged = true;
9536 if (Selection.getSelectionStart(buf) >= 0) {
9537 if (ims == null || ims.mBatchEditNesting == 0) {
9540 ims.mCursorChanged = true;
9545 if (what instanceof ParcelableSpan) {
9546 // If this is a span that can be sent to a remote process,
9547 // the current extract editor would be interested in it.
9548 if (ims != null && ims.mExtractedTextRequest != null) {
9549 if (ims.mBatchEditNesting != 0) {
9550 if (oldStart >= 0) {
9551 if (ims.mChangedStart > oldStart) {
9552 ims.mChangedStart = oldStart;
9554 if (ims.mChangedStart > oldEnd) {
9555 ims.mChangedStart = oldEnd;
9558 if (newStart >= 0) {
9559 if (ims.mChangedStart > newStart) {
9560 ims.mChangedStart = newStart;
9562 if (ims.mChangedStart > newEnd) {
9563 ims.mChangedStart = newEnd;
9567 if (DEBUG_EXTRACT) {
9568 Log.v(LOG_TAG, "Span change outside of batch: "
9569 + oldStart + "-" + oldEnd + ","
9570 + newStart + "-" + newEnd + " " + what);
9572 ims.mContentChanged = true;
9577 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0
9578 && what instanceof SpellCheckSpan) {
9579 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
9584 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
9585 if (isTemporarilyDetached()) {
9586 // If we are temporarily in the detach state, then do nothing.
9587 super.onFocusChanged(focused, direction, previouslyFocusedRect);
9591 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
9594 if (mText instanceof Spannable) {
9595 Spannable sp = (Spannable) mText;
9596 MetaKeyKeyListener.resetMetaState(sp);
9600 startStopMarquee(focused);
9602 if (mTransformation != null) {
9603 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
9606 super.onFocusChanged(focused, direction, previouslyFocusedRect);
9610 public void onWindowFocusChanged(boolean hasWindowFocus) {
9611 super.onWindowFocusChanged(hasWindowFocus);
9613 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
9615 startStopMarquee(hasWindowFocus);
9619 protected void onVisibilityChanged(View changedView, int visibility) {
9620 super.onVisibilityChanged(changedView, visibility);
9621 if (mEditor != null && visibility != VISIBLE) {
9622 mEditor.hideCursorAndSpanControllers();
9623 stopTextActionMode();
9628 * Use {@link BaseInputConnection#removeComposingSpans
9629 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
9630 * state from this text view.
9632 public void clearComposingText() {
9633 if (mText instanceof Spannable) {
9634 BaseInputConnection.removeComposingSpans((Spannable) mText);
9639 public void setSelected(boolean selected) {
9640 boolean wasSelected = isSelected();
9642 super.setSelected(selected);
9644 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
9654 public boolean onTouchEvent(MotionEvent event) {
9655 final int action = event.getActionMasked();
9656 if (mEditor != null) {
9657 mEditor.onTouchEvent(event);
9659 if (mEditor.mSelectionModifierCursorController != null
9660 && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
9665 final boolean superResult = super.onTouchEvent(event);
9668 * Don't handle the release after a long press, because it will move the selection away from
9669 * whatever the menu action was trying to affect. If the long press should have triggered an
9670 * insertion action mode, we can now actually show it.
9672 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
9673 mEditor.mDiscardNextActionUp = false;
9675 if (mEditor.mIsInsertionActionModeStartPending) {
9676 mEditor.startInsertionActionMode();
9677 mEditor.mIsInsertionActionModeStartPending = false;
9682 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP)
9683 && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
9685 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9686 && mText instanceof Spannable && mLayout != null) {
9687 boolean handled = false;
9689 if (mMovement != null) {
9690 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
9693 final boolean textIsSelectable = isTextSelectable();
9694 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
9695 // The LinkMovementMethod which should handle taps on links has not been installed
9696 // on non editable text that support text selection.
9697 // We reproduce its behavior here to open links for these.
9698 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
9699 getSelectionEnd(), ClickableSpan.class);
9701 if (links.length > 0) {
9702 links[0].onClick(this);
9707 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
9708 // Show the IME, except when selecting in read-only text.
9709 final InputMethodManager imm = InputMethodManager.peekInstance();
9711 if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9712 imm.showSoftInput(this, 0);
9715 // The above condition ensures that the mEditor is not null
9716 mEditor.onTouchUpEvent(event);
9730 public boolean onGenericMotionEvent(MotionEvent event) {
9731 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9733 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
9736 } catch (AbstractMethodError ex) {
9737 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
9738 // Ignore its absence in case third party applications implemented the
9739 // interface directly.
9742 return super.onGenericMotionEvent(event);
9746 protected void onCreateContextMenu(ContextMenu menu) {
9747 if (mEditor != null) {
9748 mEditor.onCreateContextMenu(menu);
9753 public boolean showContextMenu() {
9754 if (mEditor != null) {
9755 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
9757 return super.showContextMenu();
9761 public boolean showContextMenu(float x, float y) {
9762 if (mEditor != null) {
9763 mEditor.setContextMenuAnchor(x, y);
9765 return super.showContextMenu(x, y);
9769 * @return True iff this TextView contains a text that can be edited, or if this is
9770 * a selectable TextView.
9772 boolean isTextEditable() {
9773 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
9777 * Returns true, only while processing a touch gesture, if the initial
9778 * touch down event caused focus to move to the text view and as a result
9779 * its selection changed. Only valid while processing the touch gesture
9780 * of interest, in an editable text view.
9782 public boolean didTouchFocusSelect() {
9783 return mEditor != null && mEditor.mTouchFocusSelected;
9787 public void cancelLongPress() {
9788 super.cancelLongPress();
9789 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
9793 public boolean onTrackballEvent(MotionEvent event) {
9794 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
9795 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
9800 return super.onTrackballEvent(event);
9804 * Sets the Scroller used for producing a scrolling animation
9806 * @param s A Scroller instance
9808 public void setScroller(Scroller s) {
9813 protected float getLeftFadingEdgeStrength() {
9814 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9815 final Marquee marquee = mMarquee;
9816 if (marquee.shouldDrawLeftFade()) {
9817 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
9821 } else if (getLineCount() == 1) {
9822 final float lineLeft = getLayout().getLineLeft(0);
9823 if (lineLeft > mScrollX) return 0.0f;
9824 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
9826 return super.getLeftFadingEdgeStrength();
9830 protected float getRightFadingEdgeStrength() {
9831 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
9832 final Marquee marquee = mMarquee;
9833 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
9834 } else if (getLineCount() == 1) {
9835 final float rightEdge = mScrollX +
9836 (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight());
9837 final float lineRight = getLayout().getLineRight(0);
9838 if (lineRight < rightEdge) return 0.0f;
9839 return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
9841 return super.getRightFadingEdgeStrength();
9845 * Calculates the fading edge strength as the ratio of the distance between two
9846 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
9847 * value for the distance calculation.
9849 * @param position1 A horizontal position.
9850 * @param position2 A horizontal position.
9851 * @return Fading edge strength between [0.0f, 1.0f].
9853 @FloatRange(from = 0.0, to = 1.0)
9854 private float getHorizontalFadingEdgeStrength(float position1, float position2) {
9855 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
9856 if (horizontalFadingEdgeLength == 0) return 0.0f;
9857 final float diff = Math.abs(position1 - position2);
9858 if (diff > horizontalFadingEdgeLength) return 1.0f;
9859 return diff / horizontalFadingEdgeLength;
9862 private boolean isMarqueeFadeEnabled() {
9863 return mEllipsize == TextUtils.TruncateAt.MARQUEE
9864 && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
9868 protected int computeHorizontalScrollRange() {
9869 if (mLayout != null) {
9870 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT
9871 ? (int) mLayout.getLineWidth(0) : mLayout.getWidth();
9874 return super.computeHorizontalScrollRange();
9878 protected int computeVerticalScrollRange() {
9879 if (mLayout != null) {
9880 return mLayout.getHeight();
9882 return super.computeVerticalScrollRange();
9886 protected int computeVerticalScrollExtent() {
9887 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
9891 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
9892 super.findViewsWithText(outViews, searched, flags);
9893 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
9894 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
9895 String searchedLowerCase = searched.toString().toLowerCase();
9896 String textLowerCase = mText.toString().toLowerCase();
9897 if (textLowerCase.contains(searchedLowerCase)) {
9904 * Type of the text buffer that defines the characteristics of the text such as static,
9905 * styleable, or editable.
9907 public enum BufferType {
9908 NORMAL, SPANNABLE, EDITABLE
9912 * Returns the TextView_textColor attribute from the TypedArray, if set, or
9913 * the TextAppearance_textColor from the TextView_textAppearance attribute,
9914 * if TextView_textColor was not set directly.
9918 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
9919 if (attrs == null) {
9920 // Preserve behavior prior to removal of this API.
9921 throw new NullPointerException();
9924 // It's not safe to use this method from apps. The parameter 'attrs'
9925 // must have been obtained using the TextView filter array which is not
9926 // available to the SDK. As such, we grab a default TypedArray with the
9927 // right filter instead here.
9928 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
9929 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
9930 if (colors == null) {
9931 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
9933 final TypedArray appearance = context.obtainStyledAttributes(
9934 ap, R.styleable.TextAppearance);
9935 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
9936 appearance.recycle();
9945 * Returns the default color from the TextView_textColor attribute from the
9946 * AttributeSet, if set, or the default color from the
9947 * TextAppearance_textColor from the TextView_textAppearance attribute, if
9948 * TextView_textColor was not set directly.
9952 public static int getTextColor(Context context, TypedArray attrs, int def) {
9953 final ColorStateList colors = getTextColors(context, attrs);
9954 if (colors == null) {
9957 return colors.getDefaultColor();
9962 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
9963 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
9964 // Handle Ctrl-only shortcuts.
9966 case KeyEvent.KEYCODE_A:
9967 if (canSelectText()) {
9968 return onTextContextMenuItem(ID_SELECT_ALL);
9971 case KeyEvent.KEYCODE_Z:
9973 return onTextContextMenuItem(ID_UNDO);
9976 case KeyEvent.KEYCODE_X:
9978 return onTextContextMenuItem(ID_CUT);
9981 case KeyEvent.KEYCODE_C:
9983 return onTextContextMenuItem(ID_COPY);
9986 case KeyEvent.KEYCODE_V:
9988 return onTextContextMenuItem(ID_PASTE);
9992 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
9993 // Handle Ctrl-Shift shortcuts.
9995 case KeyEvent.KEYCODE_Z:
9997 return onTextContextMenuItem(ID_REDO);
10000 case KeyEvent.KEYCODE_V:
10002 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
10006 return super.onKeyShortcut(keyCode, event);
10010 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
10011 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
10012 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
10015 boolean canSelectText() {
10016 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
10020 * Test based on the <i>intrinsic</i> charateristics of the TextView.
10021 * The text must be spannable and the movement method must allow for arbitary selection.
10023 * See also {@link #canSelectText()}.
10025 boolean textCanBeSelected() {
10026 // prepareCursorController() relies on this method.
10027 // If you change this condition, make sure prepareCursorController is called anywhere
10028 // the value of this condition might be changed.
10029 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
10030 return isTextEditable()
10031 || (isTextSelectable() && mText instanceof Spannable && isEnabled());
10034 private Locale getTextServicesLocale(boolean allowNullLocale) {
10035 // Start fetching the text services locale asynchronously.
10036 updateTextServicesLocaleAsync();
10037 // If !allowNullLocale and there is no cached text services locale, just return the default
10039 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
10040 : mCurrentSpellCheckerLocaleCache;
10044 * This is a temporary method. Future versions may support multi-locale text.
10045 * Caveat: This method may not return the latest text services locale, but this should be
10046 * acceptable and it's more important to make this method asynchronous.
10048 * @return The locale that should be used for a word iterator
10049 * in this TextView, based on the current spell checker settings,
10050 * the current IME's locale, or the system default locale.
10051 * Please note that a word iterator in this TextView is different from another word iterator
10052 * used by SpellChecker.java of TextView. This method should be used for the former.
10055 // TODO: Support multi-locale
10056 // TODO: Update the text services locale immediately after the keyboard locale is switched
10057 // by catching intent of keyboard switch event
10058 public Locale getTextServicesLocale() {
10059 return getTextServicesLocale(false /* allowNullLocale */);
10063 * @return {@code true} if this TextView is specialized for showing and interacting with the
10064 * extracted text in a full-screen input method.
10067 public boolean isInExtractedMode() {
10072 * @return {@code true} if this widget supports auto-sizing text and has been configured to
10075 private boolean isAutoSizeEnabled() {
10076 return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE;
10080 * @return {@code true} if this TextView supports auto-sizing text to fit within its container.
10083 protected boolean supportsAutoSizeText() {
10088 * This is a temporary method. Future versions may support multi-locale text.
10089 * Caveat: This method may not return the latest spell checker locale, but this should be
10090 * acceptable and it's more important to make this method asynchronous.
10092 * @return The locale that should be used for a spell checker in this TextView,
10093 * based on the current spell checker settings, the current IME's locale, or the system default
10097 public Locale getSpellCheckerLocale() {
10098 return getTextServicesLocale(true /* allowNullLocale */);
10101 private void updateTextServicesLocaleAsync() {
10102 // AsyncTask.execute() uses a serial executor which means we don't have
10103 // to lock around updateTextServicesLocaleLocked() to prevent it from
10104 // being executed n times in parallel.
10105 AsyncTask.execute(new Runnable() {
10107 public void run() {
10108 updateTextServicesLocaleLocked();
10113 private void updateTextServicesLocaleLocked() {
10114 final TextServicesManager textServicesManager = (TextServicesManager)
10115 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
10116 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
10117 final Locale locale;
10118 if (subtype != null) {
10119 locale = subtype.getLocaleObject();
10123 mCurrentSpellCheckerLocaleCache = locale;
10126 void onLocaleChanged() {
10127 mEditor.onLocaleChanged();
10131 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
10132 * Made available to achieve a consistent behavior.
10135 public WordIterator getWordIterator() {
10136 if (mEditor != null) {
10137 return mEditor.getWordIterator();
10145 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
10146 super.onPopulateAccessibilityEventInternal(event);
10148 final CharSequence text = getTextForAccessibility();
10149 if (!TextUtils.isEmpty(text)) {
10150 event.getText().add(text);
10155 public CharSequence getAccessibilityClassName() {
10156 return TextView.class.getName();
10160 public void onProvideStructure(ViewStructure structure) {
10161 super.onProvideStructure(structure);
10162 onProvideAutoStructureForAssistOrAutofill(structure, false);
10166 public void onProvideAutofillStructure(ViewStructure structure, int flags) {
10167 super.onProvideAutofillStructure(structure, flags);
10168 onProvideAutoStructureForAssistOrAutofill(structure, true);
10171 private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure,
10172 boolean forAutofill) {
10173 final boolean isPassword = hasPasswordTransformationMethod()
10174 || isPasswordInputType(getInputType());
10176 structure.setDataIsSensitive(!mTextFromResource);
10179 if (!isPassword || forAutofill) {
10180 if (mLayout == null) {
10183 Layout layout = mLayout;
10184 final int lineCount = layout.getLineCount();
10185 if (lineCount <= 1) {
10186 // Simple case: this is a single line.
10187 final CharSequence text = getText();
10189 structure.setText(text);
10191 structure.setText(text, getSelectionStart(), getSelectionEnd());
10194 // Complex case: multi-line, could be scrolled or within a scroll container
10195 // so some lines are not visible.
10196 final int[] tmpCords = new int[2];
10197 getLocationInWindow(tmpCords);
10198 final int topWindowLocation = tmpCords[1];
10200 ViewParent viewParent = getParent();
10201 while (viewParent instanceof View) {
10202 root = (View) viewParent;
10203 viewParent = root.getParent();
10205 final int windowHeight = root.getHeight();
10207 final int bottomLine;
10208 if (topWindowLocation >= 0) {
10209 // The top of the view is fully within its window; start text at line 0.
10210 topLine = getLineAtCoordinateUnclamped(0);
10211 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1);
10213 // The top of hte window has scrolled off the top of the window; figure out
10214 // the starting line for this.
10215 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
10216 bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation);
10218 // We want to return some contextual lines above/below the lines that are
10219 // actually visible.
10220 int expandedTopLine = topLine - (bottomLine - topLine) / 2;
10221 if (expandedTopLine < 0) {
10222 expandedTopLine = 0;
10224 int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2;
10225 if (expandedBottomLine >= lineCount) {
10226 expandedBottomLine = lineCount - 1;
10229 // Convert lines into character offsets.
10230 int expandedTopChar = layout.getLineStart(expandedTopLine);
10231 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
10233 // Take into account selection -- if there is a selection, we need to expand
10234 // the text we are returning to include that selection.
10235 final int selStart = getSelectionStart();
10236 final int selEnd = getSelectionEnd();
10237 if (selStart < selEnd) {
10238 if (selStart < expandedTopChar) {
10239 expandedTopChar = selStart;
10241 if (selEnd > expandedBottomChar) {
10242 expandedBottomChar = selEnd;
10246 // Get the text and trim it to the range we are reporting.
10247 CharSequence text = getText();
10248 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
10249 text = text.subSequence(expandedTopChar, expandedBottomChar);
10253 structure.setText(text);
10255 structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar);
10257 final int[] lineOffsets = new int[bottomLine - topLine + 1];
10258 final int[] lineBaselines = new int[bottomLine - topLine + 1];
10259 final int baselineOffset = getBaselineOffset();
10260 for (int i = topLine; i <= bottomLine; i++) {
10261 lineOffsets[i - topLine] = layout.getLineStart(i);
10262 lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset;
10264 structure.setTextLines(lineOffsets, lineBaselines);
10268 if (!forAutofill) {
10269 // Extract style information that applies to the TextView as a whole.
10271 int typefaceStyle = getTypefaceStyle();
10272 if ((typefaceStyle & Typeface.BOLD) != 0) {
10273 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10275 if ((typefaceStyle & Typeface.ITALIC) != 0) {
10276 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
10279 // Global styles can also be set via TextView.setPaintFlags().
10280 int paintFlags = mTextPaint.getFlags();
10281 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
10282 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
10284 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
10285 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
10287 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
10288 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
10291 // TextView does not have its own text background color. A background is either part
10292 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
10293 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
10294 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
10297 structure.setHint(getHint());
10298 structure.setInputType(getInputType());
10301 boolean canRequestAutofill() {
10302 if (!isAutofillable()) {
10305 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10307 return afm.isEnabled();
10312 private void requestAutofill() {
10313 final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
10315 afm.requestAutofill(this);
10320 public void autofill(AutofillValue value) {
10321 if (!value.isText() || !isTextEditable()) {
10322 Log.w(LOG_TAG, value + " could not be autofilled into " + this);
10326 final CharSequence autofilledValue = value.getTextValue();
10328 // First autofill it...
10329 setText(autofilledValue, mBufferType, true, 0);
10331 // ...then move cursor to the end.
10332 final CharSequence text = getText();
10333 if ((text instanceof Spannable)) {
10334 Selection.setSelection((Spannable) text, text.length());
10339 public @AutofillType int getAutofillType() {
10340 return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
10345 public AutofillValue getAutofillValue() {
10346 return isTextEditable() ? AutofillValue.forText(getText()) : null;
10351 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
10352 super.onInitializeAccessibilityEventInternal(event);
10354 final boolean isPassword = hasPasswordTransformationMethod();
10355 event.setPassword(isPassword);
10357 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
10358 event.setFromIndex(Selection.getSelectionStart(mText));
10359 event.setToIndex(Selection.getSelectionEnd(mText));
10360 event.setItemCount(mText.length());
10366 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
10367 super.onInitializeAccessibilityNodeInfoInternal(info);
10369 final boolean isPassword = hasPasswordTransformationMethod();
10370 info.setPassword(isPassword);
10371 info.setText(getTextForAccessibility());
10372 info.setHintText(mHint);
10373 info.setShowingHintText(isShowingHint());
10375 if (mBufferType == BufferType.EDITABLE) {
10376 info.setEditable(true);
10378 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
10382 if (mEditor != null) {
10383 info.setInputType(mEditor.mInputType);
10385 if (mEditor.mError != null) {
10386 info.setContentInvalid(true);
10387 info.setError(mEditor.mError);
10391 if (!TextUtils.isEmpty(mText)) {
10392 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
10393 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
10394 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
10395 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
10396 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
10397 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
10398 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
10399 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
10400 info.setAvailableExtraData(
10401 Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
10406 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
10409 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
10412 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
10415 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
10416 ACCESSIBILITY_ACTION_SHARE,
10417 getResources().getString(com.android.internal.R.string.share)));
10419 if (canProcessText()) { // also implies mEditor is not null.
10420 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
10424 // Check for known input filter types.
10425 final int numFilters = mFilters.length;
10426 for (int i = 0; i < numFilters; i++) {
10427 final InputFilter filter = mFilters[i];
10428 if (filter instanceof InputFilter.LengthFilter) {
10429 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
10433 if (!isSingleLine()) {
10434 info.setMultiLine(true);
10439 public void addExtraDataToAccessibilityNodeInfo(
10440 AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) {
10441 // The only extra data we support requires arguments.
10442 if (arguments == null) {
10445 if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) {
10446 int positionInfoStartIndex = arguments.getInt(
10447 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1);
10448 int positionInfoLength = arguments.getInt(
10449 EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1);
10450 if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0)
10451 || (positionInfoStartIndex >= mText.length())) {
10452 Log.e(LOG_TAG, "Invalid arguments for accessibility character locations");
10455 RectF[] boundingRects = new RectF[positionInfoLength];
10456 final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder();
10457 populateCharacterBounds(builder, positionInfoStartIndex,
10458 positionInfoStartIndex + positionInfoLength,
10459 viewportToContentHorizontalOffset(), viewportToContentVerticalOffset());
10460 CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build();
10461 for (int i = 0; i < positionInfoLength; i++) {
10462 int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i);
10463 if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) {
10464 RectF bounds = cursorAnchorInfo
10465 .getCharacterBounds(positionInfoStartIndex + i);
10466 if (bounds != null) {
10467 mapRectFromViewToScreenCoords(bounds, true);
10468 boundingRects[i] = bounds;
10472 info.getExtras().putParcelableArray(extraDataKey, boundingRects);
10477 * Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
10479 * @param builder The builder to populate
10480 * @param startIndex The starting character index to populate
10481 * @param endIndex The ending character index to populate
10482 * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the
10484 * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content
10487 public void populateCharacterBounds(CursorAnchorInfo.Builder builder,
10488 int startIndex, int endIndex, float viewportToContentHorizontalOffset,
10489 float viewportToContentVerticalOffset) {
10490 final int minLine = mLayout.getLineForOffset(startIndex);
10491 final int maxLine = mLayout.getLineForOffset(endIndex - 1);
10492 for (int line = minLine; line <= maxLine; ++line) {
10493 final int lineStart = mLayout.getLineStart(line);
10494 final int lineEnd = mLayout.getLineEnd(line);
10495 final int offsetStart = Math.max(lineStart, startIndex);
10496 final int offsetEnd = Math.min(lineEnd, endIndex);
10497 final boolean ltrLine =
10498 mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT;
10499 final float[] widths = new float[offsetEnd - offsetStart];
10500 mLayout.getPaint().getTextWidths(mText, offsetStart, offsetEnd, widths);
10501 final float top = mLayout.getLineTop(line);
10502 final float bottom = mLayout.getLineBottom(line);
10503 for (int offset = offsetStart; offset < offsetEnd; ++offset) {
10504 final float charWidth = widths[offset - offsetStart];
10505 final boolean isRtl = mLayout.isRtlCharAt(offset);
10506 final float primary = mLayout.getPrimaryHorizontal(offset);
10507 final float secondary = mLayout.getSecondaryHorizontal(offset);
10508 // TODO: This doesn't work perfectly for text with custom styles and
10514 left = secondary - charWidth;
10518 right = primary + charWidth;
10523 right = secondary + charWidth;
10525 left = primary - charWidth;
10529 // TODO: Check top-right and bottom-left as well.
10530 final float localLeft = left + viewportToContentHorizontalOffset;
10531 final float localRight = right + viewportToContentHorizontalOffset;
10532 final float localTop = top + viewportToContentVerticalOffset;
10533 final float localBottom = bottom + viewportToContentVerticalOffset;
10534 final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop);
10535 final boolean isBottomRightVisible =
10536 isPositionVisible(localRight, localBottom);
10537 int characterBoundsFlags = 0;
10538 if (isTopLeftVisible || isBottomRightVisible) {
10539 characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION;
10541 if (!isTopLeftVisible || !isBottomRightVisible) {
10542 characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
10545 characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL;
10547 // Here offset is the index in Java chars.
10548 builder.addCharacterBounds(offset, localLeft, localTop, localRight,
10549 localBottom, characterBoundsFlags);
10557 public boolean isPositionVisible(final float positionX, final float positionY) {
10558 synchronized (TEMP_POSITION) {
10559 final float[] position = TEMP_POSITION;
10560 position[0] = positionX;
10561 position[1] = positionY;
10564 while (view != null) {
10565 if (view != this) {
10566 // Local scroll is already taken into account in positionX/Y
10567 position[0] -= view.getScrollX();
10568 position[1] -= view.getScrollY();
10571 if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth()
10572 || position[1] > view.getHeight()) {
10576 if (!view.getMatrix().isIdentity()) {
10577 view.getMatrix().mapPoints(position);
10580 position[0] += view.getLeft();
10581 position[1] += view.getTop();
10583 final ViewParent parent = view.getParent();
10584 if (parent instanceof View) {
10585 view = (View) parent;
10587 // We've reached the ViewRoot, stop iterating
10593 // We've been able to walk up the view hierarchy and the position was never clipped
10598 * Performs an accessibility action after it has been offered to the
10604 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
10605 if (mEditor != null
10606 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
10610 case AccessibilityNodeInfo.ACTION_CLICK: {
10611 return performAccessibilityActionClick(arguments);
10613 case AccessibilityNodeInfo.ACTION_COPY: {
10614 if (isFocused() && canCopy()) {
10615 if (onTextContextMenuItem(ID_COPY)) {
10620 case AccessibilityNodeInfo.ACTION_PASTE: {
10621 if (isFocused() && canPaste()) {
10622 if (onTextContextMenuItem(ID_PASTE)) {
10627 case AccessibilityNodeInfo.ACTION_CUT: {
10628 if (isFocused() && canCut()) {
10629 if (onTextContextMenuItem(ID_CUT)) {
10634 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
10635 ensureIterableTextForAccessibilitySelectable();
10636 CharSequence text = getIterableTextForAccessibility();
10637 if (text == null) {
10640 final int start = (arguments != null) ? arguments.getInt(
10641 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
10642 final int end = (arguments != null) ? arguments.getInt(
10643 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
10644 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
10645 // No arguments clears the selection.
10646 if (start == end && end == -1) {
10647 Selection.removeSelection((Spannable) text);
10650 if (start >= 0 && start <= end && end <= text.length()) {
10651 Selection.setSelection((Spannable) text, start, end);
10652 // Make sure selection mode is engaged.
10653 if (mEditor != null) {
10654 mEditor.startSelectionActionModeAsync(false);
10660 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
10661 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
10662 ensureIterableTextForAccessibilitySelectable();
10663 return super.performAccessibilityActionInternal(action, arguments);
10665 case ACCESSIBILITY_ACTION_SHARE: {
10666 if (isFocused() && canShare()) {
10667 if (onTextContextMenuItem(ID_SHARE)) {
10672 case AccessibilityNodeInfo.ACTION_SET_TEXT: {
10673 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
10676 CharSequence text = (arguments != null) ? arguments.getCharSequence(
10677 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
10679 if (mText != null) {
10680 int updatedTextLength = mText.length();
10681 if (updatedTextLength > 0) {
10682 Selection.setSelection((Spannable) mText, updatedTextLength);
10687 return super.performAccessibilityActionInternal(action, arguments);
10692 private boolean performAccessibilityActionClick(Bundle arguments) {
10693 boolean handled = false;
10695 if (!isEnabled()) {
10699 if (isClickable() || isLongClickable()) {
10700 // Simulate View.onTouchEvent for an ACTION_UP event
10701 if (isFocusable() && !isFocused()) {
10709 // Show the IME, except when selecting in read-only text.
10710 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
10711 && (isTextEditable() || isTextSelectable()) && isFocused()) {
10712 final InputMethodManager imm = InputMethodManager.peekInstance();
10714 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
10715 handled |= imm.showSoftInput(this, 0);
10722 private boolean hasSpannableText() {
10723 return mText != null && mText instanceof Spannable;
10728 public void sendAccessibilityEventInternal(int eventType) {
10729 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
10730 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
10733 // Do not send scroll events since first they are not interesting for
10734 // accessibility and second such events a generated too frequently.
10735 // For details see the implementation of bringTextIntoView().
10736 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
10739 super.sendAccessibilityEventInternal(eventType);
10743 * Returns the text that should be exposed to accessibility services.
10745 * This approximates what is displayed visually. If the user has specified
10746 * that accessibility services should speak passwords, this method will
10747 * bypass any password transformation method and return unobscured text.
10749 * @return the text that should be exposed to accessibility services, may
10750 * be {@code null} if no text is set
10753 private CharSequence getTextForAccessibility() {
10754 // If the text is empty, we must be showing the hint text.
10755 if (TextUtils.isEmpty(mText)) {
10759 // Otherwise, return whatever text is being displayed.
10760 return mTransformed;
10763 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
10764 int fromIndex, int removedCount, int addedCount) {
10765 AccessibilityEvent event =
10766 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
10767 event.setFromIndex(fromIndex);
10768 event.setRemovedCount(removedCount);
10769 event.setAddedCount(addedCount);
10770 event.setBeforeText(beforeText);
10771 sendAccessibilityEventUnchecked(event);
10775 * Returns whether this text view is a current input method target. The
10776 * default implementation just checks with {@link InputMethodManager}.
10777 * @return True if the TextView is a current input method target; false otherwise.
10779 public boolean isInputMethodTarget() {
10780 InputMethodManager imm = InputMethodManager.peekInstance();
10781 return imm != null && imm.isActive(this);
10784 static final int ID_SELECT_ALL = android.R.id.selectAll;
10785 static final int ID_UNDO = android.R.id.undo;
10786 static final int ID_REDO = android.R.id.redo;
10787 static final int ID_CUT = android.R.id.cut;
10788 static final int ID_COPY = android.R.id.copy;
10789 static final int ID_PASTE = android.R.id.paste;
10790 static final int ID_SHARE = android.R.id.shareText;
10791 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
10792 static final int ID_REPLACE = android.R.id.replaceText;
10793 static final int ID_ASSIST = android.R.id.textAssist;
10794 static final int ID_AUTOFILL = android.R.id.autofill;
10797 * Called when a context menu option for the text view is selected. Currently
10798 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
10799 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
10801 * @return true if the context menu item action was performed.
10803 public boolean onTextContextMenuItem(int id) {
10805 int max = mText.length();
10808 final int selStart = getSelectionStart();
10809 final int selEnd = getSelectionEnd();
10811 min = Math.max(0, Math.min(selStart, selEnd));
10812 max = Math.max(0, Math.max(selStart, selEnd));
10816 case ID_SELECT_ALL:
10817 final boolean hadSelection = hasSelection();
10819 if (mEditor != null && hadSelection) {
10820 mEditor.invalidateActionModeAsync();
10825 if (mEditor != null) {
10828 return true; // Returns true even if nothing was undone.
10831 if (mEditor != null) {
10834 return true; // Returns true even if nothing was undone.
10837 paste(min, max, true /* withFormatting */);
10840 case ID_PASTE_AS_PLAIN_TEXT:
10841 paste(min, max, false /* withFormatting */);
10845 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10846 deleteText_internal(min, max);
10850 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
10851 stopTextActionMode();
10855 if (mEditor != null) {
10861 shareSelectedText();
10866 stopTextActionMode();
10872 CharSequence getTransformedText(int start, int end) {
10873 return removeSuggestionSpans(mTransformed.subSequence(start, end));
10877 public boolean performLongClick() {
10878 boolean handled = false;
10879 boolean performedHapticFeedback = false;
10881 if (mEditor != null) {
10882 mEditor.mIsBeingLongClicked = true;
10885 if (super.performLongClick()) {
10887 performedHapticFeedback = true;
10890 if (mEditor != null) {
10891 handled |= mEditor.performLongClick(handled);
10892 mEditor.mIsBeingLongClicked = false;
10896 if (!performedHapticFeedback) {
10897 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
10899 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
10901 MetricsLogger.action(
10903 MetricsEvent.TEXT_LONGPRESS,
10904 TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER);
10911 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
10912 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
10913 if (mEditor != null) {
10914 mEditor.onScrollChanged();
10919 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
10920 * by the IME or by the spell checker as the user types. This is done by adding
10921 * {@link SuggestionSpan}s to the text.
10923 * When suggestions are enabled (default), this list of suggestions will be displayed when the
10924 * user asks for them on these parts of the text. This value depends on the inputType of this
10927 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
10929 * In addition, the type variation must be one of
10930 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
10931 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
10932 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
10933 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
10934 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
10936 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
10938 * @return true if the suggestions popup window is enabled, based on the inputType.
10940 public boolean isSuggestionsEnabled() {
10941 if (mEditor == null) return false;
10942 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
10945 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
10947 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
10948 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL
10949 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT
10950 || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE
10951 || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE
10952 || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
10956 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10957 * selection is initiated in this View.
10959 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
10960 * Paste, Replace and Share actions, depending on what this View supports.
10962 * <p>A custom implementation can add new entries in the default menu in its
10963 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
10964 * method. The default actions can also be removed from the menu using
10965 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
10966 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
10967 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
10969 * <p>Returning false from
10970 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
10971 * will prevent the action mode from being started.
10973 * <p>Action click events should be handled by the custom implementation of
10974 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
10975 * android.view.MenuItem)}.
10977 * <p>Note that text selection mode is not started when a TextView receives focus and the
10978 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
10979 * that case, to allow for quick replacement.
10981 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
10982 createEditorIfNeeded();
10983 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
10987 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
10989 * @return The current custom selection callback.
10991 public ActionMode.Callback getCustomSelectionActionModeCallback() {
10992 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
10996 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
10997 * insertion is initiated in this View.
10998 * The standard implementation populates the menu with a subset of Select All,
10999 * Paste and Replace actions, depending on what this View supports.
11001 * <p>A custom implementation can add new entries in the default menu in its
11002 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
11003 * android.view.Menu)} method. The default actions can also be removed from the menu using
11004 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
11005 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
11007 * <p>Returning false from
11008 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
11009 * android.view.Menu)} will prevent the action mode from being started.</p>
11011 * <p>Action click events should be handled by the custom implementation of
11012 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
11013 * android.view.MenuItem)}.</p>
11015 * <p>Note that text insertion mode is not started when a TextView receives focus and the
11016 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
11018 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
11019 createEditorIfNeeded();
11020 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
11024 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
11026 * @return The current custom insertion callback.
11028 public ActionMode.Callback getCustomInsertionActionModeCallback() {
11029 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
11033 * Sets the {@link TextClassifier} for this TextView.
11035 public void setTextClassifier(@Nullable TextClassifier textClassifier) {
11036 mTextClassifier = textClassifier;
11040 * Returns the {@link TextClassifier} used by this TextView.
11041 * If no TextClassifier has been set, this TextView uses the default set by the
11042 * {@link TextClassificationManager}.
11045 public TextClassifier getTextClassifier() {
11046 if (mTextClassifier == null) {
11047 TextClassificationManager tcm =
11048 mContext.getSystemService(TextClassificationManager.class);
11050 mTextClassifier = tcm.getTextClassifier();
11052 mTextClassifier = TextClassifier.NO_OP;
11055 return mTextClassifier;
11061 protected void stopTextActionMode() {
11062 if (mEditor != null) {
11063 mEditor.stopTextActionMode();
11067 boolean canUndo() {
11068 return mEditor != null && mEditor.canUndo();
11071 boolean canRedo() {
11072 return mEditor != null && mEditor.canRedo();
11076 if (hasPasswordTransformationMethod()) {
11080 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null
11081 && mEditor.mKeyListener != null) {
11088 boolean canCopy() {
11089 if (hasPasswordTransformationMethod()) {
11093 if (mText.length() > 0 && hasSelection() && mEditor != null) {
11100 boolean canShare() {
11101 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
11107 boolean isDeviceProvisioned() {
11108 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
11109 mDeviceProvisionedState = Settings.Global.getInt(
11110 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
11111 ? DEVICE_PROVISIONED_YES
11112 : DEVICE_PROVISIONED_NO;
11114 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
11117 boolean canPaste() {
11118 return (mText instanceof Editable
11119 && mEditor != null && mEditor.mKeyListener != null
11120 && getSelectionStart() >= 0
11121 && getSelectionEnd() >= 0
11122 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11123 .hasPrimaryClip());
11126 boolean canPasteAsPlainText() {
11131 final ClipData clipData =
11132 ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE))
11134 final ClipDescription description = clipData.getDescription();
11135 final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
11136 final CharSequence text = clipData.getItemAt(0).getText();
11137 if (isPlainType && (text instanceof Spanned)) {
11138 Spanned spanned = (Spanned) text;
11139 if (TextUtils.hasStyleSpan(spanned)) {
11143 return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
11146 boolean canProcessText() {
11147 if (getId() == View.NO_ID) {
11153 boolean canSelectAllText() {
11154 return canSelectText() && !hasPasswordTransformationMethod()
11155 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
11158 boolean selectAllText() {
11159 if (mEditor != null) {
11160 // Hide the toolbar before changing the selection to avoid flickering.
11161 mEditor.hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
11163 final int length = mText.length();
11164 Selection.setSelection((Spannable) mText, 0, length);
11168 void replaceSelectionWithText(CharSequence text) {
11169 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
11173 * Paste clipboard content between min and max positions.
11175 private void paste(int min, int max, boolean withFormatting) {
11176 ClipboardManager clipboard =
11177 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11178 ClipData clip = clipboard.getPrimaryClip();
11179 if (clip != null) {
11180 boolean didFirst = false;
11181 for (int i = 0; i < clip.getItemCount(); i++) {
11182 final CharSequence paste;
11183 if (withFormatting) {
11184 paste = clip.getItemAt(i).coerceToStyledText(getContext());
11186 // Get an item as text and remove all spans by toString().
11187 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
11188 paste = (text instanceof Spanned) ? text.toString() : text;
11190 if (paste != null) {
11192 Selection.setSelection((Spannable) mText, max);
11193 ((Editable) mText).replace(min, max, paste);
11196 ((Editable) mText).insert(getSelectionEnd(), "\n");
11197 ((Editable) mText).insert(getSelectionEnd(), paste);
11201 sLastCutCopyOrTextChangedTime = 0;
11205 private void shareSelectedText() {
11206 String selectedText = getSelectedText();
11207 if (selectedText != null && !selectedText.isEmpty()) {
11208 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
11209 sharingIntent.setType("text/plain");
11210 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
11211 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
11212 getContext().startActivity(Intent.createChooser(sharingIntent, null));
11213 Selection.setSelection((Spannable) mText, getSelectionEnd());
11217 private void setPrimaryClip(ClipData clip) {
11218 ClipboardManager clipboard =
11219 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
11220 clipboard.setPrimaryClip(clip);
11221 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
11225 * Get the character offset closest to the specified absolute position. A typical use case is to
11226 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
11228 * @param x The horizontal absolute position of a point on screen
11229 * @param y The vertical absolute position of a point on screen
11230 * @return the character offset for the character whose position is closest to the specified
11231 * position. Returns -1 if there is no layout.
11233 public int getOffsetForPosition(float x, float y) {
11234 if (getLayout() == null) return -1;
11235 final int line = getLineAtCoordinate(y);
11236 final int offset = getOffsetAtCoordinate(line, x);
11240 float convertToLocalHorizontalCoordinate(float x) {
11241 x -= getTotalPaddingLeft();
11242 // Clamp the position to inside of the view.
11243 x = Math.max(0.0f, x);
11244 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
11249 int getLineAtCoordinate(float y) {
11250 y -= getTotalPaddingTop();
11251 // Clamp the position to inside of the view.
11252 y = Math.max(0.0f, y);
11253 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
11255 return getLayout().getLineForVertical((int) y);
11258 int getLineAtCoordinateUnclamped(float y) {
11259 y -= getTotalPaddingTop();
11261 return getLayout().getLineForVertical((int) y);
11264 int getOffsetAtCoordinate(int line, float x) {
11265 x = convertToLocalHorizontalCoordinate(x);
11266 return getLayout().getOffsetForHorizontal(line, x);
11270 public boolean onDragEvent(DragEvent event) {
11271 switch (event.getAction()) {
11272 case DragEvent.ACTION_DRAG_STARTED:
11273 return mEditor != null && mEditor.hasInsertionController();
11275 case DragEvent.ACTION_DRAG_ENTERED:
11276 TextView.this.requestFocus();
11279 case DragEvent.ACTION_DRAG_LOCATION:
11280 if (mText instanceof Spannable) {
11281 final int offset = getOffsetForPosition(event.getX(), event.getY());
11282 Selection.setSelection((Spannable) mText, offset);
11286 case DragEvent.ACTION_DROP:
11287 if (mEditor != null) mEditor.onDrop(event);
11290 case DragEvent.ACTION_DRAG_ENDED:
11291 case DragEvent.ACTION_DRAG_EXITED:
11297 boolean isInBatchEditMode() {
11298 if (mEditor == null) return false;
11299 final Editor.InputMethodState ims = mEditor.mInputMethodState;
11301 return ims.mBatchEditNesting > 0;
11303 return mEditor.mInBatchEditControllers;
11307 public void onRtlPropertiesChanged(int layoutDirection) {
11308 super.onRtlPropertiesChanged(layoutDirection);
11310 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
11311 if (mTextDir != newTextDir) {
11312 mTextDir = newTextDir;
11313 if (mLayout != null) {
11314 checkForRelayout();
11322 protected TextDirectionHeuristic getTextDirectionHeuristic() {
11323 if (hasPasswordTransformationMethod()) {
11324 // passwords fields should be LTR
11325 return TextDirectionHeuristics.LTR;
11328 if (mEditor != null
11329 && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
11330 == EditorInfo.TYPE_CLASS_PHONE) {
11331 // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
11332 // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
11334 final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
11335 final String zero = symbols.getDigitStrings()[0];
11336 // In case the zero digit is multi-codepoint, just use the first codepoint to determine
11338 final int firstCodepoint = zero.codePointAt(0);
11339 final byte digitDirection = Character.getDirectionality(firstCodepoint);
11340 if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
11341 || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
11342 return TextDirectionHeuristics.RTL;
11344 return TextDirectionHeuristics.LTR;
11348 // Always need to resolve layout direction first
11349 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
11351 // Now, we can select the heuristic
11352 switch (getTextDirection()) {
11354 case TEXT_DIRECTION_FIRST_STRONG:
11355 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
11356 TextDirectionHeuristics.FIRSTSTRONG_LTR);
11357 case TEXT_DIRECTION_ANY_RTL:
11358 return TextDirectionHeuristics.ANYRTL_LTR;
11359 case TEXT_DIRECTION_LTR:
11360 return TextDirectionHeuristics.LTR;
11361 case TEXT_DIRECTION_RTL:
11362 return TextDirectionHeuristics.RTL;
11363 case TEXT_DIRECTION_LOCALE:
11364 return TextDirectionHeuristics.LOCALE;
11365 case TEXT_DIRECTION_FIRST_STRONG_LTR:
11366 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
11367 case TEXT_DIRECTION_FIRST_STRONG_RTL:
11368 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
11376 public void onResolveDrawables(int layoutDirection) {
11377 // No need to resolve twice
11378 if (mLastLayoutDirection == layoutDirection) {
11381 mLastLayoutDirection = layoutDirection;
11383 // Resolve drawables
11384 if (mDrawables != null) {
11385 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
11386 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
11387 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
11388 applyCompoundDrawableTint();
11394 * Prepares a drawable for display by propagating layout direction and
11397 * @param dr the drawable to prepare
11399 private void prepareDrawableForDisplay(@Nullable Drawable dr) {
11404 dr.setLayoutDirection(getLayoutDirection());
11406 if (dr.isStateful()) {
11407 dr.setState(getDrawableState());
11408 dr.jumpToCurrentState();
11415 protected void resetResolvedDrawables() {
11416 super.resetResolvedDrawables();
11417 mLastLayoutDirection = -1;
11423 protected void viewClicked(InputMethodManager imm) {
11425 imm.viewClicked(this);
11430 * Deletes the range of text [start, end[.
11433 protected void deleteText_internal(int start, int end) {
11434 ((Editable) mText).delete(start, end);
11438 * Replaces the range of text [start, end[ by replacement text
11441 protected void replaceText_internal(int start, int end, CharSequence text) {
11442 ((Editable) mText).replace(start, end, text);
11446 * Sets a span on the specified range of text
11449 protected void setSpan_internal(Object span, int start, int end, int flags) {
11450 ((Editable) mText).setSpan(span, start, end, flags);
11454 * Moves the cursor to the specified offset position in text
11457 protected void setCursorPosition_internal(int start, int end) {
11458 Selection.setSelection(((Editable) mText), start, end);
11462 * An Editor should be created as soon as any of the editable-specific fields (grouped
11463 * inside the Editor object) is assigned to a non-default value.
11464 * This method will create the Editor if needed.
11466 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
11467 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
11468 * Editor for backward compatibility, as soon as one of these fields is assigned.
11470 * Also note that for performance reasons, the mEditor is created when needed, but not
11471 * reset when no more edit-specific fields are needed.
11473 private void createEditorIfNeeded() {
11474 if (mEditor == null) {
11475 mEditor = new Editor(this);
11483 public CharSequence getIterableTextForAccessibility() {
11487 private void ensureIterableTextForAccessibilitySelectable() {
11488 if (!(mText instanceof Spannable)) {
11489 setText(mText, BufferType.SPANNABLE);
11497 public TextSegmentIterator getIteratorForGranularity(int granularity) {
11498 switch (granularity) {
11499 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
11500 Spannable text = (Spannable) getIterableTextForAccessibility();
11501 if (!TextUtils.isEmpty(text) && getLayout() != null) {
11502 AccessibilityIterators.LineTextSegmentIterator iterator =
11503 AccessibilityIterators.LineTextSegmentIterator.getInstance();
11504 iterator.initialize(text, getLayout());
11508 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
11509 Spannable text = (Spannable) getIterableTextForAccessibility();
11510 if (!TextUtils.isEmpty(text) && getLayout() != null) {
11511 AccessibilityIterators.PageTextSegmentIterator iterator =
11512 AccessibilityIterators.PageTextSegmentIterator.getInstance();
11513 iterator.initialize(this);
11518 return super.getIteratorForGranularity(granularity);
11525 public int getAccessibilitySelectionStart() {
11526 return getSelectionStart();
11532 public boolean isAccessibilitySelectionExtendable() {
11540 public int getAccessibilitySelectionEnd() {
11541 return getSelectionEnd();
11548 public void setAccessibilitySelection(int start, int end) {
11549 if (getAccessibilitySelectionStart() == start
11550 && getAccessibilitySelectionEnd() == end) {
11553 CharSequence text = getIterableTextForAccessibility();
11554 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
11555 Selection.setSelection((Spannable) text, start, end);
11557 Selection.removeSelection((Spannable) text);
11559 // Hide all selection controllers used for adjusting selection
11560 // since we are doing so explicitlty by other means and these
11561 // controllers interact with how selection behaves.
11562 if (mEditor != null) {
11563 mEditor.hideCursorAndSpanControllers();
11564 mEditor.stopTextActionMode();
11570 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
11571 super.encodeProperties(stream);
11573 TruncateAt ellipsize = getEllipsize();
11574 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
11575 stream.addProperty("text:textSize", getTextSize());
11576 stream.addProperty("text:scaledTextSize", getScaledTextSize());
11577 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
11578 stream.addProperty("text:selectionStart", getSelectionStart());
11579 stream.addProperty("text:selectionEnd", getSelectionEnd());
11580 stream.addProperty("text:curTextColor", mCurTextColor);
11581 stream.addProperty("text:text", mText == null ? null : mText.toString());
11582 stream.addProperty("text:gravity", mGravity);
11586 * User interface state that is stored by TextView for implementing
11587 * {@link View#onSaveInstanceState}.
11589 public static class SavedState extends BaseSavedState {
11593 boolean frozenWithFocus;
11594 CharSequence error;
11595 ParcelableParcel editorState; // Optional state from Editor.
11597 SavedState(Parcelable superState) {
11602 public void writeToParcel(Parcel out, int flags) {
11603 super.writeToParcel(out, flags);
11604 out.writeInt(selStart);
11605 out.writeInt(selEnd);
11606 out.writeInt(frozenWithFocus ? 1 : 0);
11607 TextUtils.writeToParcel(text, out, flags);
11609 if (error == null) {
11613 TextUtils.writeToParcel(error, out, flags);
11616 if (editorState == null) {
11620 editorState.writeToParcel(out, flags);
11625 public String toString() {
11626 String str = "TextView.SavedState{"
11627 + Integer.toHexString(System.identityHashCode(this))
11628 + " start=" + selStart + " end=" + selEnd;
11629 if (text != null) {
11630 str += " text=" + text;
11635 @SuppressWarnings("hiding")
11636 public static final Parcelable.Creator<SavedState> CREATOR =
11637 new Parcelable.Creator<SavedState>() {
11638 public SavedState createFromParcel(Parcel in) {
11639 return new SavedState(in);
11642 public SavedState[] newArray(int size) {
11643 return new SavedState[size];
11647 private SavedState(Parcel in) {
11649 selStart = in.readInt();
11650 selEnd = in.readInt();
11651 frozenWithFocus = (in.readInt() != 0);
11652 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11654 if (in.readInt() != 0) {
11655 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
11658 if (in.readInt() != 0) {
11659 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
11664 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
11665 private char[] mChars;
11666 private int mStart, mLength;
11668 public CharWrapper(char[] chars, int start, int len) {
11674 /* package */ void set(char[] chars, int start, int len) {
11680 public int length() {
11684 public char charAt(int off) {
11685 return mChars[off + mStart];
11689 public String toString() {
11690 return new String(mChars, mStart, mLength);
11693 public CharSequence subSequence(int start, int end) {
11694 if (start < 0 || end < 0 || start > mLength || end > mLength) {
11695 throw new IndexOutOfBoundsException(start + ", " + end);
11698 return new String(mChars, start + mStart, end - start);
11701 public void getChars(int start, int end, char[] buf, int off) {
11702 if (start < 0 || end < 0 || start > mLength || end > mLength) {
11703 throw new IndexOutOfBoundsException(start + ", " + end);
11706 System.arraycopy(mChars, start + mStart, buf, off, end - start);
11710 public void drawText(BaseCanvas c, int start, int end,
11711 float x, float y, Paint p) {
11712 c.drawText(mChars, start + mStart, end - start, x, y, p);
11716 public void drawTextRun(BaseCanvas c, int start, int end,
11717 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
11718 int count = end - start;
11719 int contextCount = contextEnd - contextStart;
11720 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
11721 contextCount, x, y, isRtl, p);
11724 public float measureText(int start, int end, Paint p) {
11725 return p.measureText(mChars, start + mStart, end - start);
11728 public int getTextWidths(int start, int end, float[] widths, Paint p) {
11729 return p.getTextWidths(mChars, start + mStart, end - start, widths);
11732 public float getTextRunAdvances(int start, int end, int contextStart,
11733 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
11735 int count = end - start;
11736 int contextCount = contextEnd - contextStart;
11737 return p.getTextRunAdvances(mChars, start + mStart, count,
11738 contextStart + mStart, contextCount, isRtl, advances,
11742 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
11743 int offset, int cursorOpt, Paint p) {
11744 int contextCount = contextEnd - contextStart;
11745 return p.getTextRunCursor(mChars, contextStart + mStart,
11746 contextCount, dir, offset + mStart, cursorOpt);
11750 private static final class Marquee {
11751 // TODO: Add an option to configure this
11752 private static final float MARQUEE_DELTA_MAX = 0.07f;
11753 private static final int MARQUEE_DELAY = 1200;
11754 private static final int MARQUEE_DP_PER_SECOND = 30;
11756 private static final byte MARQUEE_STOPPED = 0x0;
11757 private static final byte MARQUEE_STARTING = 0x1;
11758 private static final byte MARQUEE_RUNNING = 0x2;
11760 private final WeakReference<TextView> mView;
11761 private final Choreographer mChoreographer;
11763 private byte mStatus = MARQUEE_STOPPED;
11764 private final float mPixelsPerSecond;
11765 private float mMaxScroll;
11766 private float mMaxFadeScroll;
11767 private float mGhostStart;
11768 private float mGhostOffset;
11769 private float mFadeStop;
11770 private int mRepeatLimit;
11772 private float mScroll;
11773 private long mLastAnimationMs;
11775 Marquee(TextView v) {
11776 final float density = v.getContext().getResources().getDisplayMetrics().density;
11777 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
11778 mView = new WeakReference<TextView>(v);
11779 mChoreographer = Choreographer.getInstance();
11782 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
11784 public void doFrame(long frameTimeNanos) {
11789 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
11791 public void doFrame(long frameTimeNanos) {
11792 mStatus = MARQUEE_RUNNING;
11793 mLastAnimationMs = mChoreographer.getFrameTime();
11798 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
11800 public void doFrame(long frameTimeNanos) {
11801 if (mStatus == MARQUEE_RUNNING) {
11802 if (mRepeatLimit >= 0) {
11805 start(mRepeatLimit);
11811 if (mStatus != MARQUEE_RUNNING) {
11815 mChoreographer.removeFrameCallback(mTickCallback);
11817 final TextView textView = mView.get();
11818 if (textView != null && (textView.isFocused() || textView.isSelected())) {
11819 long currentMs = mChoreographer.getFrameTime();
11820 long deltaMs = currentMs - mLastAnimationMs;
11821 mLastAnimationMs = currentMs;
11822 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
11823 mScroll += deltaPx;
11824 if (mScroll > mMaxScroll) {
11825 mScroll = mMaxScroll;
11826 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
11828 mChoreographer.postFrameCallback(mTickCallback);
11830 textView.invalidate();
11835 mStatus = MARQUEE_STOPPED;
11836 mChoreographer.removeFrameCallback(mStartCallback);
11837 mChoreographer.removeFrameCallback(mRestartCallback);
11838 mChoreographer.removeFrameCallback(mTickCallback);
11842 private void resetScroll() {
11844 final TextView textView = mView.get();
11845 if (textView != null) textView.invalidate();
11848 void start(int repeatLimit) {
11849 if (repeatLimit == 0) {
11853 mRepeatLimit = repeatLimit;
11854 final TextView textView = mView.get();
11855 if (textView != null && textView.mLayout != null) {
11856 mStatus = MARQUEE_STARTING;
11858 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
11859 - textView.getCompoundPaddingRight();
11860 final float lineWidth = textView.mLayout.getLineWidth(0);
11861 final float gap = textWidth / 3.0f;
11862 mGhostStart = lineWidth - textWidth + gap;
11863 mMaxScroll = mGhostStart + textWidth;
11864 mGhostOffset = lineWidth + gap;
11865 mFadeStop = lineWidth + textWidth / 6.0f;
11866 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
11868 textView.invalidate();
11869 mChoreographer.postFrameCallback(mStartCallback);
11873 float getGhostOffset() {
11874 return mGhostOffset;
11877 float getScroll() {
11881 float getMaxFadeScroll() {
11882 return mMaxFadeScroll;
11885 boolean shouldDrawLeftFade() {
11886 return mScroll <= mFadeStop;
11889 boolean shouldDrawGhost() {
11890 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
11893 boolean isRunning() {
11894 return mStatus == MARQUEE_RUNNING;
11897 boolean isStopped() {
11898 return mStatus == MARQUEE_STOPPED;
11902 private class ChangeWatcher implements TextWatcher, SpanWatcher {
11904 private CharSequence mBeforeText;
11906 public void beforeTextChanged(CharSequence buffer, int start,
11907 int before, int after) {
11908 if (DEBUG_EXTRACT) {
11909 Log.v(LOG_TAG, "beforeTextChanged start=" + start
11910 + " before=" + before + " after=" + after + ": " + buffer);
11913 if (AccessibilityManager.getInstance(mContext).isEnabled()
11914 && !isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) {
11915 mBeforeText = buffer.toString();
11918 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
11921 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
11922 if (DEBUG_EXTRACT) {
11923 Log.v(LOG_TAG, "onTextChanged start=" + start
11924 + " before=" + before + " after=" + after + ": " + buffer);
11926 TextView.this.handleTextChanged(buffer, start, before, after);
11928 if (AccessibilityManager.getInstance(mContext).isEnabled()
11929 && (isFocused() || isSelected() && isShown())) {
11930 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
11931 mBeforeText = null;
11935 public void afterTextChanged(Editable buffer) {
11936 if (DEBUG_EXTRACT) {
11937 Log.v(LOG_TAG, "afterTextChanged: " + buffer);
11939 TextView.this.sendAfterTextChanged(buffer);
11941 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
11942 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
11946 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
11947 if (DEBUG_EXTRACT) {
11948 Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
11949 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
11951 TextView.this.spanChange(buf, what, s, st, e, en);
11954 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
11955 if (DEBUG_EXTRACT) {
11956 Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf);
11958 TextView.this.spanChange(buf, what, -1, s, -1, e);
11961 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
11962 if (DEBUG_EXTRACT) {
11963 Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf);
11965 TextView.this.spanChange(buf, what, s, -1, e, -1);