2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
19 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
22 import android.annotation.ColorInt;
23 import android.annotation.DrawableRes;
24 import android.annotation.FloatRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.Size;
28 import android.annotation.StringRes;
29 import android.annotation.StyleRes;
30 import android.annotation.XmlRes;
31 import android.app.Activity;
32 import android.app.assist.AssistStructure;
33 import android.content.ClipData;
34 import android.content.ClipboardManager;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.UndoManager;
38 import android.content.res.ColorStateList;
39 import android.content.res.CompatibilityInfo;
40 import android.content.res.Configuration;
41 import android.content.res.Resources;
42 import android.content.res.TypedArray;
43 import android.content.res.XmlResourceParser;
44 import android.graphics.Canvas;
45 import android.graphics.Insets;
46 import android.graphics.Paint;
47 import android.graphics.Path;
48 import android.graphics.PorterDuff;
49 import android.graphics.Rect;
50 import android.graphics.RectF;
51 import android.graphics.Typeface;
52 import android.graphics.drawable.Drawable;
53 import android.os.AsyncTask;
54 import android.os.Bundle;
55 import android.os.LocaleList;
56 import android.os.Parcel;
57 import android.os.Parcelable;
58 import android.os.ParcelableParcel;
59 import android.os.SystemClock;
60 import android.os.UserHandle;
61 import android.provider.Settings;
62 import android.text.BoringLayout;
63 import android.text.DynamicLayout;
64 import android.text.Editable;
65 import android.text.GetChars;
66 import android.text.GraphicsOperations;
67 import android.text.InputFilter;
68 import android.text.InputType;
69 import android.text.Layout;
70 import android.text.ParcelableSpan;
71 import android.text.Selection;
72 import android.text.SpanWatcher;
73 import android.text.Spannable;
74 import android.text.SpannableString;
75 import android.text.SpannableStringBuilder;
76 import android.text.Spanned;
77 import android.text.SpannedString;
78 import android.text.StaticLayout;
79 import android.text.TextDirectionHeuristic;
80 import android.text.TextDirectionHeuristics;
81 import android.text.TextPaint;
82 import android.text.TextUtils;
83 import android.text.TextUtils.TruncateAt;
84 import android.text.TextWatcher;
85 import android.text.method.AllCapsTransformationMethod;
86 import android.text.method.ArrowKeyMovementMethod;
87 import android.text.method.DateKeyListener;
88 import android.text.method.DateTimeKeyListener;
89 import android.text.method.DialerKeyListener;
90 import android.text.method.DigitsKeyListener;
91 import android.text.method.KeyListener;
92 import android.text.method.LinkMovementMethod;
93 import android.text.method.MetaKeyKeyListener;
94 import android.text.method.MovementMethod;
95 import android.text.method.PasswordTransformationMethod;
96 import android.text.method.SingleLineTransformationMethod;
97 import android.text.method.TextKeyListener;
98 import android.text.method.TimeKeyListener;
99 import android.text.method.TransformationMethod;
100 import android.text.method.TransformationMethod2;
101 import android.text.method.WordIterator;
102 import android.text.style.CharacterStyle;
103 import android.text.style.ClickableSpan;
104 import android.text.style.ParagraphStyle;
105 import android.text.style.SpellCheckSpan;
106 import android.text.style.SuggestionSpan;
107 import android.text.style.URLSpan;
108 import android.text.style.UpdateAppearance;
109 import android.text.util.Linkify;
110 import android.util.AttributeSet;
111 import android.util.Log;
112 import android.util.TypedValue;
113 import android.view.AccessibilityIterators.TextSegmentIterator;
114 import android.view.ActionMode;
115 import android.view.Choreographer;
116 import android.view.ContextMenu;
117 import android.view.DragEvent;
118 import android.view.Gravity;
119 import android.view.HapticFeedbackConstants;
120 import android.view.KeyCharacterMap;
121 import android.view.KeyEvent;
122 import android.view.MotionEvent;
123 import android.view.PointerIcon;
124 import android.view.View;
125 import android.view.ViewConfiguration;
126 import android.view.ViewDebug;
127 import android.view.ViewGroup.LayoutParams;
128 import android.view.ViewHierarchyEncoder;
129 import android.view.ViewParent;
130 import android.view.ViewRootImpl;
131 import android.view.ViewStructure;
132 import android.view.ViewTreeObserver;
133 import android.view.accessibility.AccessibilityEvent;
134 import android.view.accessibility.AccessibilityManager;
135 import android.view.accessibility.AccessibilityNodeInfo;
136 import android.view.animation.AnimationUtils;
137 import android.view.inputmethod.BaseInputConnection;
138 import android.view.inputmethod.CompletionInfo;
139 import android.view.inputmethod.CorrectionInfo;
140 import android.view.inputmethod.EditorInfo;
141 import android.view.inputmethod.ExtractedText;
142 import android.view.inputmethod.ExtractedTextRequest;
143 import android.view.inputmethod.InputConnection;
144 import android.view.inputmethod.InputMethodManager;
145 import android.view.textservice.SpellCheckerSubtype;
146 import android.view.textservice.TextServicesManager;
147 import android.widget.RemoteViews.RemoteView;
149 import com.android.internal.annotations.VisibleForTesting;
150 import com.android.internal.util.FastMath;
151 import com.android.internal.widget.EditableInputConnection;
153 import org.xmlpull.v1.XmlPullParserException;
155 import java.io.IOException;
156 import java.lang.ref.WeakReference;
157 import java.util.ArrayList;
158 import java.util.Locale;
161 * Displays text to the user and optionally allows them to edit it. A TextView
162 * is a complete text editor, however the basic class is configured to not
163 * allow editing; see {@link EditText} for a subclass that configures the text
167 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
168 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
169 * android:textIsSelectable} to "true" or call
170 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
171 * allows users to make selection gestures in the TextView, which in turn triggers the system's
172 * built-in copy/paste controls.
174 * <b>XML attributes</b>
176 * See {@link android.R.styleable#TextView TextView Attributes},
177 * {@link android.R.styleable#View View Attributes}
179 * @attr ref android.R.styleable#TextView_text
180 * @attr ref android.R.styleable#TextView_bufferType
181 * @attr ref android.R.styleable#TextView_hint
182 * @attr ref android.R.styleable#TextView_textColor
183 * @attr ref android.R.styleable#TextView_textColorHighlight
184 * @attr ref android.R.styleable#TextView_textColorHint
185 * @attr ref android.R.styleable#TextView_textAppearance
186 * @attr ref android.R.styleable#TextView_textColorLink
187 * @attr ref android.R.styleable#TextView_textSize
188 * @attr ref android.R.styleable#TextView_textScaleX
189 * @attr ref android.R.styleable#TextView_fontFamily
190 * @attr ref android.R.styleable#TextView_typeface
191 * @attr ref android.R.styleable#TextView_textStyle
192 * @attr ref android.R.styleable#TextView_cursorVisible
193 * @attr ref android.R.styleable#TextView_maxLines
194 * @attr ref android.R.styleable#TextView_maxHeight
195 * @attr ref android.R.styleable#TextView_lines
196 * @attr ref android.R.styleable#TextView_height
197 * @attr ref android.R.styleable#TextView_minLines
198 * @attr ref android.R.styleable#TextView_minHeight
199 * @attr ref android.R.styleable#TextView_maxEms
200 * @attr ref android.R.styleable#TextView_maxWidth
201 * @attr ref android.R.styleable#TextView_ems
202 * @attr ref android.R.styleable#TextView_width
203 * @attr ref android.R.styleable#TextView_minEms
204 * @attr ref android.R.styleable#TextView_minWidth
205 * @attr ref android.R.styleable#TextView_gravity
206 * @attr ref android.R.styleable#TextView_scrollHorizontally
207 * @attr ref android.R.styleable#TextView_password
208 * @attr ref android.R.styleable#TextView_singleLine
209 * @attr ref android.R.styleable#TextView_selectAllOnFocus
210 * @attr ref android.R.styleable#TextView_includeFontPadding
211 * @attr ref android.R.styleable#TextView_maxLength
212 * @attr ref android.R.styleable#TextView_shadowColor
213 * @attr ref android.R.styleable#TextView_shadowDx
214 * @attr ref android.R.styleable#TextView_shadowDy
215 * @attr ref android.R.styleable#TextView_shadowRadius
216 * @attr ref android.R.styleable#TextView_autoLink
217 * @attr ref android.R.styleable#TextView_linksClickable
218 * @attr ref android.R.styleable#TextView_numeric
219 * @attr ref android.R.styleable#TextView_digits
220 * @attr ref android.R.styleable#TextView_phoneNumber
221 * @attr ref android.R.styleable#TextView_inputMethod
222 * @attr ref android.R.styleable#TextView_capitalize
223 * @attr ref android.R.styleable#TextView_autoText
224 * @attr ref android.R.styleable#TextView_editable
225 * @attr ref android.R.styleable#TextView_freezesText
226 * @attr ref android.R.styleable#TextView_ellipsize
227 * @attr ref android.R.styleable#TextView_drawableTop
228 * @attr ref android.R.styleable#TextView_drawableBottom
229 * @attr ref android.R.styleable#TextView_drawableRight
230 * @attr ref android.R.styleable#TextView_drawableLeft
231 * @attr ref android.R.styleable#TextView_drawableStart
232 * @attr ref android.R.styleable#TextView_drawableEnd
233 * @attr ref android.R.styleable#TextView_drawablePadding
234 * @attr ref android.R.styleable#TextView_drawableTint
235 * @attr ref android.R.styleable#TextView_drawableTintMode
236 * @attr ref android.R.styleable#TextView_lineSpacingExtra
237 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
238 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
239 * @attr ref android.R.styleable#TextView_inputType
240 * @attr ref android.R.styleable#TextView_imeOptions
241 * @attr ref android.R.styleable#TextView_privateImeOptions
242 * @attr ref android.R.styleable#TextView_imeActionLabel
243 * @attr ref android.R.styleable#TextView_imeActionId
244 * @attr ref android.R.styleable#TextView_editorExtras
245 * @attr ref android.R.styleable#TextView_elegantTextHeight
246 * @attr ref android.R.styleable#TextView_letterSpacing
247 * @attr ref android.R.styleable#TextView_fontFeatureSettings
248 * @attr ref android.R.styleable#TextView_breakStrategy
249 * @attr ref android.R.styleable#TextView_hyphenationFrequency
252 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
253 static final String LOG_TAG = "TextView";
254 static final boolean DEBUG_EXTRACT = false;
256 // Enum for the "typeface" XML parameter.
257 // TODO: How can we get this from the XML instead of hardcoding it here?
258 private static final int SANS = 1;
259 private static final int SERIF = 2;
260 private static final int MONOSPACE = 3;
262 // Bitfield for the "numeric" XML parameter.
263 // TODO: How can we get this from the XML instead of hardcoding it here?
264 private static final int SIGNED = 2;
265 private static final int DECIMAL = 4;
268 * Draw marquee text with fading edges as usual
270 private static final int MARQUEE_FADE_NORMAL = 0;
273 * Draw marquee text as ellipsize end while inactive instead of with the fade.
274 * (Useful for devices where the fade can be expensive if overdone)
276 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
279 * Draw marquee text with fading edges because it is currently active/animating.
281 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
283 private static final int LINES = 1;
284 private static final int EMS = LINES;
285 private static final int PIXELS = 2;
287 private static final RectF TEMP_RECTF = new RectF();
290 static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
291 private static final int ANIMATED_SCROLL_GAP = 250;
293 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
294 private static final Spanned EMPTY_SPANNED = new SpannedString("");
296 private static final int CHANGE_WATCHER_PRIORITY = 100;
298 // New state used to change background based on whether this TextView is multiline.
299 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
301 // Accessibility action to share selected text.
302 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
307 // Accessibility action start id for "process text" actions.
308 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
313 static final int PROCESS_TEXT_REQUEST_CODE = 100;
316 * Return code of {@link #doKeyDown}.
318 private static final int KEY_EVENT_NOT_HANDLED = 0;
319 private static final int KEY_EVENT_HANDLED = -1;
320 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
321 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
323 // System wide time for last cut, copy or text changed action.
324 static long sLastCutCopyOrTextChangedTime;
326 private ColorStateList mTextColor;
327 private ColorStateList mHintTextColor;
328 private ColorStateList mLinkTextColor;
329 @ViewDebug.ExportedProperty(category = "text")
330 private int mCurTextColor;
331 private int mCurHintTextColor;
332 private boolean mFreezesText;
334 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
335 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
337 private float mShadowRadius, mShadowDx, mShadowDy;
338 private int mShadowColor;
340 private boolean mPreDrawRegistered;
341 private boolean mPreDrawListenerDetached;
343 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
344 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
345 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
346 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
347 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
348 private boolean mPreventDefaultMovement;
350 private TextUtils.TruncateAt mEllipsize;
352 static class Drawables {
353 static final int LEFT = 0;
354 static final int TOP = 1;
355 static final int RIGHT = 2;
356 static final int BOTTOM = 3;
358 static final int DRAWABLE_NONE = -1;
359 static final int DRAWABLE_RIGHT = 0;
360 static final int DRAWABLE_LEFT = 1;
362 final Rect mCompoundRect = new Rect();
364 final Drawable[] mShowing = new Drawable[4];
366 ColorStateList mTintList;
367 PorterDuff.Mode mTintMode;
369 boolean mHasTintMode;
371 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
372 Drawable mDrawableLeftInitial, mDrawableRightInitial;
374 boolean mIsRtlCompatibilityMode;
377 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
378 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
380 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
381 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
383 int mDrawablePadding;
385 int mDrawableSaved = DRAWABLE_NONE;
387 public Drawables(Context context) {
388 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
389 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
390 !context.getApplicationInfo().hasRtlSupport());
395 * @return {@code true} if this object contains metadata that needs to
396 * be retained, {@code false} otherwise
398 public boolean hasMetadata() {
399 return mDrawablePadding != 0 || mHasTintMode || mHasTint;
403 * Updates the list of displayed drawables to account for the current
406 * @param layoutDirection the current layout direction
407 * @return {@code true} if the displayed drawables changed
409 public boolean resolveWithLayoutDirection(int layoutDirection) {
410 final Drawable previousLeft = mShowing[Drawables.LEFT];
411 final Drawable previousRight = mShowing[Drawables.RIGHT];
413 // First reset "left" and "right" drawables to their initial values
414 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
415 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
417 if (mIsRtlCompatibilityMode) {
418 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
419 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
420 mShowing[Drawables.LEFT] = mDrawableStart;
421 mDrawableSizeLeft = mDrawableSizeStart;
422 mDrawableHeightLeft = mDrawableHeightStart;
424 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
425 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
426 mShowing[Drawables.RIGHT] = mDrawableEnd;
427 mDrawableSizeRight = mDrawableSizeEnd;
428 mDrawableHeightRight = mDrawableHeightEnd;
431 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
432 // drawable if and only if they have been defined
433 switch(layoutDirection) {
434 case LAYOUT_DIRECTION_RTL:
436 mShowing[Drawables.RIGHT] = mDrawableStart;
437 mDrawableSizeRight = mDrawableSizeStart;
438 mDrawableHeightRight = mDrawableHeightStart;
440 mShowing[Drawables.LEFT] = mDrawableEnd;
441 mDrawableSizeLeft = mDrawableSizeEnd;
442 mDrawableHeightLeft = mDrawableHeightEnd;
446 case LAYOUT_DIRECTION_LTR:
449 mShowing[Drawables.LEFT] = mDrawableStart;
450 mDrawableSizeLeft = mDrawableSizeStart;
451 mDrawableHeightLeft = mDrawableHeightStart;
453 mShowing[Drawables.RIGHT] = mDrawableEnd;
454 mDrawableSizeRight = mDrawableSizeEnd;
455 mDrawableHeightRight = mDrawableHeightEnd;
461 applyErrorDrawableIfNeeded(layoutDirection);
463 return mShowing[Drawables.LEFT] != previousLeft
464 || mShowing[Drawables.RIGHT] != previousRight;
467 public void setErrorDrawable(Drawable dr, TextView tv) {
468 if (mDrawableError != dr && mDrawableError != null) {
469 mDrawableError.setCallback(null);
473 if (mDrawableError != null) {
474 final Rect compoundRect = mCompoundRect;
475 final int[] state = tv.getDrawableState();
477 mDrawableError.setState(state);
478 mDrawableError.copyBounds(compoundRect);
479 mDrawableError.setCallback(tv);
480 mDrawableSizeError = compoundRect.width();
481 mDrawableHeightError = compoundRect.height();
483 mDrawableSizeError = mDrawableHeightError = 0;
487 private void applyErrorDrawableIfNeeded(int layoutDirection) {
488 // first restore the initial state if needed
489 switch (mDrawableSaved) {
491 mShowing[Drawables.LEFT] = mDrawableTemp;
492 mDrawableSizeLeft = mDrawableSizeTemp;
493 mDrawableHeightLeft = mDrawableHeightTemp;
496 mShowing[Drawables.RIGHT] = mDrawableTemp;
497 mDrawableSizeRight = mDrawableSizeTemp;
498 mDrawableHeightRight = mDrawableHeightTemp;
503 // then, if needed, assign the Error drawable to the correct location
504 if (mDrawableError != null) {
505 switch(layoutDirection) {
506 case LAYOUT_DIRECTION_RTL:
507 mDrawableSaved = DRAWABLE_LEFT;
509 mDrawableTemp = mShowing[Drawables.LEFT];
510 mDrawableSizeTemp = mDrawableSizeLeft;
511 mDrawableHeightTemp = mDrawableHeightLeft;
513 mShowing[Drawables.LEFT] = mDrawableError;
514 mDrawableSizeLeft = mDrawableSizeError;
515 mDrawableHeightLeft = mDrawableHeightError;
517 case LAYOUT_DIRECTION_LTR:
519 mDrawableSaved = DRAWABLE_RIGHT;
521 mDrawableTemp = mShowing[Drawables.RIGHT];
522 mDrawableSizeTemp = mDrawableSizeRight;
523 mDrawableHeightTemp = mDrawableHeightRight;
525 mShowing[Drawables.RIGHT] = mDrawableError;
526 mDrawableSizeRight = mDrawableSizeError;
527 mDrawableHeightRight = mDrawableHeightError;
534 Drawables mDrawables;
536 private CharWrapper mCharWrapper;
538 private Marquee mMarquee;
539 private boolean mRestartMarquee;
541 private int mMarqueeRepeatLimit = 3;
543 private int mLastLayoutDirection = -1;
546 * On some devices the fading edges add a performance penalty if used
547 * extensively in the same layout. This mode indicates how the marquee
548 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
550 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
553 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
554 * the layout that should be used when the mode switches.
556 private Layout mSavedMarqueeModeLayout;
558 @ViewDebug.ExportedProperty(category = "text")
559 private CharSequence mText;
560 private CharSequence mTransformed;
561 private BufferType mBufferType = BufferType.NORMAL;
563 private CharSequence mHint;
564 private Layout mHintLayout;
566 private MovementMethod mMovement;
568 private TransformationMethod mTransformation;
569 private boolean mAllowTransformationLengthChange;
570 private ChangeWatcher mChangeWatcher;
572 private ArrayList<TextWatcher> mListeners;
574 // display attributes
575 private final TextPaint mTextPaint;
576 private boolean mUserSetTextScaleX;
577 private Layout mLayout;
578 private boolean mLocalesChanged = false;
580 @ViewDebug.ExportedProperty(category = "text")
581 private int mGravity = Gravity.TOP | Gravity.START;
582 private boolean mHorizontallyScrolling;
584 private int mAutoLinkMask;
585 private boolean mLinksClickable = true;
587 private float mSpacingMult = 1.0f;
588 private float mSpacingAdd = 0.0f;
590 private int mBreakStrategy;
591 private int mHyphenationFrequency;
593 private int mMaximum = Integer.MAX_VALUE;
594 private int mMaxMode = LINES;
595 private int mMinimum = 0;
596 private int mMinMode = LINES;
598 private int mOldMaximum = mMaximum;
599 private int mOldMaxMode = mMaxMode;
601 private int mMaxWidth = Integer.MAX_VALUE;
602 private int mMaxWidthMode = PIXELS;
603 private int mMinWidth = 0;
604 private int mMinWidthMode = PIXELS;
606 private boolean mSingleLine;
607 private int mDesiredHeightAtMeasure = -1;
608 private boolean mIncludePad = true;
609 private int mDeferScroll = -1;
611 // tmp primitives, so we don't alloc them on each draw
612 private Rect mTempRect;
613 private long mLastScroll;
614 private Scroller mScroller;
616 private BoringLayout.Metrics mBoring, mHintBoring;
617 private BoringLayout mSavedLayout, mSavedHintLayout;
619 private TextDirectionHeuristic mTextDir;
621 private InputFilter[] mFilters = NO_FILTERS;
623 private volatile Locale mCurrentSpellCheckerLocaleCache;
625 // It is possible to have a selection even when mEditor is null (programmatically set, like when
626 // a link is pressed). These highlight-related fields do not go in mEditor.
627 int mHighlightColor = 0x6633B5E5;
628 private Path mHighlightPath;
629 private final Paint mHighlightPaint;
630 private boolean mHighlightPathBogus = true;
632 // Although these fields are specific to editable text, they are not added to Editor because
633 // they are defined by the TextView's style and are theme-dependent.
634 int mCursorDrawableRes;
635 // These six fields, could be moved to Editor, since we know their default values and we
636 // could condition the creation of the Editor to a non standard value. This is however
637 // brittle since the hardcoded values here (such as
638 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
639 // default style is modified.
640 int mTextSelectHandleLeftRes;
641 int mTextSelectHandleRightRes;
642 int mTextSelectHandleRes;
643 int mTextEditSuggestionItemLayout;
644 int mTextEditSuggestionContainerLayout;
645 int mTextEditSuggestionHighlightStyle;
648 * EditText specific data, created on demand when one of the Editor fields is used.
649 * See {@link #createEditorIfNeeded()}.
651 private Editor mEditor;
653 private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
654 private static final int DEVICE_PROVISIONED_NO = 1;
655 private static final int DEVICE_PROVISIONED_YES = 2;
658 * Some special options such as sharing selected text should only be shown if the device
659 * is provisioned. Only check the provisioned state once for a given view instance.
661 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
664 * Kick-start the font cache for the zygote process (to pay the cost of
665 * initializing freetype for our default font only once).
668 public static void preloadFontCache() {
669 Paint p = new Paint();
670 p.setAntiAlias(true);
671 // We don't care about the result, just the side-effect of measuring.
676 * Interface definition for a callback to be invoked when an action is
677 * performed on the editor.
679 public interface OnEditorActionListener {
681 * Called when an action is being performed.
683 * @param v The view that was clicked.
684 * @param actionId Identifier of the action. This will be either the
685 * identifier you supplied, or {@link EditorInfo#IME_NULL
686 * EditorInfo.IME_NULL} if being called due to the enter key
688 * @param event If triggered by an enter key, this is the event;
689 * otherwise, this is null.
690 * @return Return true if you have consumed the action, else false.
692 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
695 public TextView(Context context) {
699 public TextView(Context context, @Nullable AttributeSet attrs) {
700 this(context, attrs, com.android.internal.R.attr.textViewStyle);
703 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
704 this(context, attrs, defStyleAttr, 0);
707 @SuppressWarnings("deprecation")
709 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
710 super(context, attrs, defStyleAttr, defStyleRes);
714 final Resources res = getResources();
715 final CompatibilityInfo compat = res.getCompatibilityInfo();
717 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
718 mTextPaint.density = res.getDisplayMetrics().density;
719 mTextPaint.setCompatibilityScaling(compat.applicationScale);
721 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
722 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
724 mMovement = getDefaultMovementMethod();
726 mTransformation = null;
728 int textColorHighlight = 0;
729 ColorStateList textColor = null;
730 ColorStateList textColorHint = null;
731 ColorStateList textColorLink = null;
733 String fontFamily = null;
734 boolean fontFamilyExplicit = false;
735 int typefaceIndex = -1;
737 boolean allCaps = false;
739 float dx = 0, dy = 0, r = 0;
740 boolean elegant = false;
741 float letterSpacing = 0;
742 String fontFeatureSettings = null;
743 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
744 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
746 final Resources.Theme theme = context.getTheme();
749 * Look the appearance up without checking first if it exists because
750 * almost every TextView has one and it greatly simplifies the logic
751 * to be able to parse the appearance first and then let specific tags
752 * for this View override it.
754 TypedArray a = theme.obtainStyledAttributes(attrs,
755 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
756 TypedArray appearance = null;
757 int ap = a.getResourceId(
758 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
761 appearance = theme.obtainStyledAttributes(
762 ap, com.android.internal.R.styleable.TextAppearance);
764 if (appearance != null) {
765 int n = appearance.getIndexCount();
766 for (int i = 0; i < n; i++) {
767 int attr = appearance.getIndex(i);
770 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
771 textColorHighlight = appearance.getColor(attr, textColorHighlight);
774 case com.android.internal.R.styleable.TextAppearance_textColor:
775 textColor = appearance.getColorStateList(attr);
778 case com.android.internal.R.styleable.TextAppearance_textColorHint:
779 textColorHint = appearance.getColorStateList(attr);
782 case com.android.internal.R.styleable.TextAppearance_textColorLink:
783 textColorLink = appearance.getColorStateList(attr);
786 case com.android.internal.R.styleable.TextAppearance_textSize:
787 textSize = appearance.getDimensionPixelSize(attr, textSize);
790 case com.android.internal.R.styleable.TextAppearance_typeface:
791 typefaceIndex = appearance.getInt(attr, -1);
794 case com.android.internal.R.styleable.TextAppearance_fontFamily:
795 fontFamily = appearance.getString(attr);
798 case com.android.internal.R.styleable.TextAppearance_textStyle:
799 styleIndex = appearance.getInt(attr, -1);
802 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
803 allCaps = appearance.getBoolean(attr, false);
806 case com.android.internal.R.styleable.TextAppearance_shadowColor:
807 shadowcolor = appearance.getInt(attr, 0);
810 case com.android.internal.R.styleable.TextAppearance_shadowDx:
811 dx = appearance.getFloat(attr, 0);
814 case com.android.internal.R.styleable.TextAppearance_shadowDy:
815 dy = appearance.getFloat(attr, 0);
818 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
819 r = appearance.getFloat(attr, 0);
822 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
823 elegant = appearance.getBoolean(attr, false);
826 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
827 letterSpacing = appearance.getFloat(attr, 0);
830 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
831 fontFeatureSettings = appearance.getString(attr);
836 appearance.recycle();
839 boolean editable = getDefaultEditable();
840 CharSequence inputMethod = null;
842 CharSequence digits = null;
843 boolean phone = false;
844 boolean autotext = false;
847 boolean selectallonfocus = false;
848 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
849 drawableBottom = null, drawableStart = null, drawableEnd = null;
850 ColorStateList drawableTint = null;
851 PorterDuff.Mode drawableTintMode = null;
852 int drawablePadding = 0;
854 boolean singleLine = false;
856 CharSequence text = "";
857 CharSequence hint = null;
858 boolean password = false;
859 int inputType = EditorInfo.TYPE_NULL;
861 a = theme.obtainStyledAttributes(
862 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
864 int n = a.getIndexCount();
865 for (int i = 0; i < n; i++) {
866 int attr = a.getIndex(i);
869 case com.android.internal.R.styleable.TextView_editable:
870 editable = a.getBoolean(attr, editable);
873 case com.android.internal.R.styleable.TextView_inputMethod:
874 inputMethod = a.getText(attr);
877 case com.android.internal.R.styleable.TextView_numeric:
878 numeric = a.getInt(attr, numeric);
881 case com.android.internal.R.styleable.TextView_digits:
882 digits = a.getText(attr);
885 case com.android.internal.R.styleable.TextView_phoneNumber:
886 phone = a.getBoolean(attr, phone);
889 case com.android.internal.R.styleable.TextView_autoText:
890 autotext = a.getBoolean(attr, autotext);
893 case com.android.internal.R.styleable.TextView_capitalize:
894 autocap = a.getInt(attr, autocap);
897 case com.android.internal.R.styleable.TextView_bufferType:
898 buffertype = a.getInt(attr, buffertype);
901 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
902 selectallonfocus = a.getBoolean(attr, selectallonfocus);
905 case com.android.internal.R.styleable.TextView_autoLink:
906 mAutoLinkMask = a.getInt(attr, 0);
909 case com.android.internal.R.styleable.TextView_linksClickable:
910 mLinksClickable = a.getBoolean(attr, true);
913 case com.android.internal.R.styleable.TextView_drawableLeft:
914 drawableLeft = a.getDrawable(attr);
917 case com.android.internal.R.styleable.TextView_drawableTop:
918 drawableTop = a.getDrawable(attr);
921 case com.android.internal.R.styleable.TextView_drawableRight:
922 drawableRight = a.getDrawable(attr);
925 case com.android.internal.R.styleable.TextView_drawableBottom:
926 drawableBottom = a.getDrawable(attr);
929 case com.android.internal.R.styleable.TextView_drawableStart:
930 drawableStart = a.getDrawable(attr);
933 case com.android.internal.R.styleable.TextView_drawableEnd:
934 drawableEnd = a.getDrawable(attr);
937 case com.android.internal.R.styleable.TextView_drawableTint:
938 drawableTint = a.getColorStateList(attr);
941 case com.android.internal.R.styleable.TextView_drawableTintMode:
942 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
945 case com.android.internal.R.styleable.TextView_drawablePadding:
946 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
949 case com.android.internal.R.styleable.TextView_maxLines:
950 setMaxLines(a.getInt(attr, -1));
953 case com.android.internal.R.styleable.TextView_maxHeight:
954 setMaxHeight(a.getDimensionPixelSize(attr, -1));
957 case com.android.internal.R.styleable.TextView_lines:
958 setLines(a.getInt(attr, -1));
961 case com.android.internal.R.styleable.TextView_height:
962 setHeight(a.getDimensionPixelSize(attr, -1));
965 case com.android.internal.R.styleable.TextView_minLines:
966 setMinLines(a.getInt(attr, -1));
969 case com.android.internal.R.styleable.TextView_minHeight:
970 setMinHeight(a.getDimensionPixelSize(attr, -1));
973 case com.android.internal.R.styleable.TextView_maxEms:
974 setMaxEms(a.getInt(attr, -1));
977 case com.android.internal.R.styleable.TextView_maxWidth:
978 setMaxWidth(a.getDimensionPixelSize(attr, -1));
981 case com.android.internal.R.styleable.TextView_ems:
982 setEms(a.getInt(attr, -1));
985 case com.android.internal.R.styleable.TextView_width:
986 setWidth(a.getDimensionPixelSize(attr, -1));
989 case com.android.internal.R.styleable.TextView_minEms:
990 setMinEms(a.getInt(attr, -1));
993 case com.android.internal.R.styleable.TextView_minWidth:
994 setMinWidth(a.getDimensionPixelSize(attr, -1));
997 case com.android.internal.R.styleable.TextView_gravity:
998 setGravity(a.getInt(attr, -1));
1001 case com.android.internal.R.styleable.TextView_hint:
1002 hint = a.getText(attr);
1005 case com.android.internal.R.styleable.TextView_text:
1006 text = a.getText(attr);
1009 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1010 if (a.getBoolean(attr, false)) {
1011 setHorizontallyScrolling(true);
1015 case com.android.internal.R.styleable.TextView_singleLine:
1016 singleLine = a.getBoolean(attr, singleLine);
1019 case com.android.internal.R.styleable.TextView_ellipsize:
1020 ellipsize = a.getInt(attr, ellipsize);
1023 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1024 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1027 case com.android.internal.R.styleable.TextView_includeFontPadding:
1028 if (!a.getBoolean(attr, true)) {
1029 setIncludeFontPadding(false);
1033 case com.android.internal.R.styleable.TextView_cursorVisible:
1034 if (!a.getBoolean(attr, true)) {
1035 setCursorVisible(false);
1039 case com.android.internal.R.styleable.TextView_maxLength:
1040 maxlength = a.getInt(attr, -1);
1043 case com.android.internal.R.styleable.TextView_textScaleX:
1044 setTextScaleX(a.getFloat(attr, 1.0f));
1047 case com.android.internal.R.styleable.TextView_freezesText:
1048 mFreezesText = a.getBoolean(attr, false);
1051 case com.android.internal.R.styleable.TextView_shadowColor:
1052 shadowcolor = a.getInt(attr, 0);
1055 case com.android.internal.R.styleable.TextView_shadowDx:
1056 dx = a.getFloat(attr, 0);
1059 case com.android.internal.R.styleable.TextView_shadowDy:
1060 dy = a.getFloat(attr, 0);
1063 case com.android.internal.R.styleable.TextView_shadowRadius:
1064 r = a.getFloat(attr, 0);
1067 case com.android.internal.R.styleable.TextView_enabled:
1068 setEnabled(a.getBoolean(attr, isEnabled()));
1071 case com.android.internal.R.styleable.TextView_textColorHighlight:
1072 textColorHighlight = a.getColor(attr, textColorHighlight);
1075 case com.android.internal.R.styleable.TextView_textColor:
1076 textColor = a.getColorStateList(attr);
1079 case com.android.internal.R.styleable.TextView_textColorHint:
1080 textColorHint = a.getColorStateList(attr);
1083 case com.android.internal.R.styleable.TextView_textColorLink:
1084 textColorLink = a.getColorStateList(attr);
1087 case com.android.internal.R.styleable.TextView_textSize:
1088 textSize = a.getDimensionPixelSize(attr, textSize);
1091 case com.android.internal.R.styleable.TextView_typeface:
1092 typefaceIndex = a.getInt(attr, typefaceIndex);
1095 case com.android.internal.R.styleable.TextView_textStyle:
1096 styleIndex = a.getInt(attr, styleIndex);
1099 case com.android.internal.R.styleable.TextView_fontFamily:
1100 fontFamily = a.getString(attr);
1101 fontFamilyExplicit = true;
1104 case com.android.internal.R.styleable.TextView_password:
1105 password = a.getBoolean(attr, password);
1108 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1109 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1112 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1113 mSpacingMult = a.getFloat(attr, mSpacingMult);
1116 case com.android.internal.R.styleable.TextView_inputType:
1117 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1120 case com.android.internal.R.styleable.TextView_allowUndo:
1121 createEditorIfNeeded();
1122 mEditor.mAllowUndo = a.getBoolean(attr, true);
1125 case com.android.internal.R.styleable.TextView_imeOptions:
1126 createEditorIfNeeded();
1127 mEditor.createInputContentTypeIfNeeded();
1128 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1129 mEditor.mInputContentType.imeOptions);
1132 case com.android.internal.R.styleable.TextView_imeActionLabel:
1133 createEditorIfNeeded();
1134 mEditor.createInputContentTypeIfNeeded();
1135 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1138 case com.android.internal.R.styleable.TextView_imeActionId:
1139 createEditorIfNeeded();
1140 mEditor.createInputContentTypeIfNeeded();
1141 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1142 mEditor.mInputContentType.imeActionId);
1145 case com.android.internal.R.styleable.TextView_privateImeOptions:
1146 setPrivateImeOptions(a.getString(attr));
1149 case com.android.internal.R.styleable.TextView_editorExtras:
1151 setInputExtras(a.getResourceId(attr, 0));
1152 } catch (XmlPullParserException e) {
1153 Log.w(LOG_TAG, "Failure reading input extras", e);
1154 } catch (IOException e) {
1155 Log.w(LOG_TAG, "Failure reading input extras", e);
1159 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1160 mCursorDrawableRes = a.getResourceId(attr, 0);
1163 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1164 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1167 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1168 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1171 case com.android.internal.R.styleable.TextView_textSelectHandle:
1172 mTextSelectHandleRes = a.getResourceId(attr, 0);
1175 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1176 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1179 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1180 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1183 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1184 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1187 case com.android.internal.R.styleable.TextView_textIsSelectable:
1188 setTextIsSelectable(a.getBoolean(attr, false));
1191 case com.android.internal.R.styleable.TextView_textAllCaps:
1192 allCaps = a.getBoolean(attr, false);
1195 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1196 elegant = a.getBoolean(attr, false);
1199 case com.android.internal.R.styleable.TextView_letterSpacing:
1200 letterSpacing = a.getFloat(attr, 0);
1203 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1204 fontFeatureSettings = a.getString(attr);
1207 case com.android.internal.R.styleable.TextView_breakStrategy:
1208 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1211 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1212 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1218 BufferType bufferType = BufferType.EDITABLE;
1220 final int variation =
1221 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1222 final boolean passwordInputType = variation
1223 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1224 final boolean webPasswordInputType = variation
1225 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1226 final boolean numberPasswordInputType = variation
1227 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1229 if (inputMethod != null) {
1233 c = Class.forName(inputMethod.toString());
1234 } catch (ClassNotFoundException ex) {
1235 throw new RuntimeException(ex);
1239 createEditorIfNeeded();
1240 mEditor.mKeyListener = (KeyListener) c.newInstance();
1241 } catch (InstantiationException ex) {
1242 throw new RuntimeException(ex);
1243 } catch (IllegalAccessException ex) {
1244 throw new RuntimeException(ex);
1247 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1249 : mEditor.mKeyListener.getInputType();
1250 } catch (IncompatibleClassChangeError e) {
1251 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1253 } else if (digits != null) {
1254 createEditorIfNeeded();
1255 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1256 // If no input type was specified, we will default to generic
1257 // text, since we can't tell the IME about the set of digits
1258 // that was selected.
1259 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1260 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1261 } else if (inputType != EditorInfo.TYPE_NULL) {
1262 setInputType(inputType, true);
1263 // If set, the input type overrides what was set using the deprecated singleLine flag.
1264 singleLine = !isMultilineInputType(inputType);
1266 createEditorIfNeeded();
1267 mEditor.mKeyListener = DialerKeyListener.getInstance();
1268 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1269 } else if (numeric != 0) {
1270 createEditorIfNeeded();
1271 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1272 (numeric & DECIMAL) != 0);
1273 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1274 if ((numeric & SIGNED) != 0) {
1275 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1277 if ((numeric & DECIMAL) != 0) {
1278 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1280 mEditor.mInputType = inputType;
1281 } else if (autotext || autocap != -1) {
1282 TextKeyListener.Capitalize cap;
1284 inputType = EditorInfo.TYPE_CLASS_TEXT;
1288 cap = TextKeyListener.Capitalize.SENTENCES;
1289 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1293 cap = TextKeyListener.Capitalize.WORDS;
1294 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1298 cap = TextKeyListener.Capitalize.CHARACTERS;
1299 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1303 cap = TextKeyListener.Capitalize.NONE;
1307 createEditorIfNeeded();
1308 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1309 mEditor.mInputType = inputType;
1310 } else if (isTextSelectable()) {
1311 // Prevent text changes from keyboard.
1312 if (mEditor != null) {
1313 mEditor.mKeyListener = null;
1314 mEditor.mInputType = EditorInfo.TYPE_NULL;
1316 bufferType = BufferType.SPANNABLE;
1317 // So that selection can be changed using arrow keys and touch is handled.
1318 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1319 } else if (editable) {
1320 createEditorIfNeeded();
1321 mEditor.mKeyListener = TextKeyListener.getInstance();
1322 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1324 if (mEditor != null) mEditor.mKeyListener = null;
1326 switch (buffertype) {
1328 bufferType = BufferType.NORMAL;
1331 bufferType = BufferType.SPANNABLE;
1334 bufferType = BufferType.EDITABLE;
1339 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1340 webPasswordInputType, numberPasswordInputType);
1342 if (selectallonfocus) {
1343 createEditorIfNeeded();
1344 mEditor.mSelectAllOnFocus = true;
1346 if (bufferType == BufferType.NORMAL)
1347 bufferType = BufferType.SPANNABLE;
1350 // Set up the tint (if needed) before setting the drawables so that it
1351 // gets applied correctly.
1352 if (drawableTint != null || drawableTintMode != null) {
1353 if (mDrawables == null) {
1354 mDrawables = new Drawables(context);
1356 if (drawableTint != null) {
1357 mDrawables.mTintList = drawableTint;
1358 mDrawables.mHasTint = true;
1360 if (drawableTintMode != null) {
1361 mDrawables.mTintMode = drawableTintMode;
1362 mDrawables.mHasTintMode = true;
1366 // This call will save the initial left/right drawables
1367 setCompoundDrawablesWithIntrinsicBounds(
1368 drawableLeft, drawableTop, drawableRight, drawableBottom);
1369 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1370 setCompoundDrawablePadding(drawablePadding);
1372 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1373 // of lines of height are unchanged for multi-line TextViews.
1374 setInputTypeSingleLine(singleLine);
1375 applySingleLine(singleLine, singleLine, singleLine);
1377 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1378 ellipsize = 3; // END
1381 switch (ellipsize) {
1383 setEllipsize(TextUtils.TruncateAt.START);
1386 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1389 setEllipsize(TextUtils.TruncateAt.END);
1392 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1393 setHorizontalFadingEdgeEnabled(true);
1394 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1396 setHorizontalFadingEdgeEnabled(false);
1397 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1399 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1403 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1404 setHintTextColor(textColorHint);
1405 setLinkTextColor(textColorLink);
1406 if (textColorHighlight != 0) {
1407 setHighlightColor(textColorHighlight);
1409 setRawTextSize(textSize);
1410 setElegantTextHeight(elegant);
1411 setLetterSpacing(letterSpacing);
1412 setFontFeatureSettings(fontFeatureSettings);
1415 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1418 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1419 setTransformationMethod(PasswordTransformationMethod.getInstance());
1420 typefaceIndex = MONOSPACE;
1421 } else if (mEditor != null &&
1422 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1423 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1424 typefaceIndex = MONOSPACE;
1427 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1430 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1432 if (shadowcolor != 0) {
1433 setShadowLayer(r, dx, dy, shadowcolor);
1436 if (maxlength >= 0) {
1437 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1439 setFilters(NO_FILTERS);
1442 setText(text, bufferType);
1443 if (hint != null) setHint(hint);
1446 * Views are not normally focusable unless specified to be.
1447 * However, TextViews that have input or movement methods *are*
1448 * focusable by default.
1450 a = context.obtainStyledAttributes(
1451 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1453 boolean focusable = mMovement != null || getKeyListener() != null;
1454 boolean clickable = focusable || isClickable();
1455 boolean longClickable = focusable || isLongClickable();
1457 n = a.getIndexCount();
1458 for (int i = 0; i < n; i++) {
1459 int attr = a.getIndex(i);
1462 case com.android.internal.R.styleable.View_focusable:
1463 focusable = a.getBoolean(attr, focusable);
1466 case com.android.internal.R.styleable.View_clickable:
1467 clickable = a.getBoolean(attr, clickable);
1470 case com.android.internal.R.styleable.View_longClickable:
1471 longClickable = a.getBoolean(attr, longClickable);
1477 setFocusable(focusable);
1478 setClickable(clickable);
1479 setLongClickable(longClickable);
1481 if (mEditor != null) mEditor.prepareCursorControllers();
1483 // If not explicitly specified this view is important for accessibility.
1484 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1485 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1489 private int[] parseDimensionArray(TypedArray dimens) {
1490 if (dimens == null) {
1493 int[] result = new int[dimens.length()];
1494 for (int i = 0; i < result.length; i++) {
1495 result[i] = dimens.getDimensionPixelSize(i, 0);
1504 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1505 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1506 if (resultCode == Activity.RESULT_OK && data != null) {
1507 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1508 if (result != null) {
1509 if (isTextEditable()) {
1510 replaceSelectionWithText(result);
1511 if (mEditor != null) {
1512 mEditor.refreshTextActionMode();
1515 if (result.length() > 0) {
1516 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1521 } else if (mText instanceof Spannable) {
1522 // Reset the selection.
1523 Selection.setSelection((Spannable) mText, getSelectionEnd());
1528 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1530 if (familyName != null) {
1531 tf = Typeface.create(familyName, styleIndex);
1537 switch (typefaceIndex) {
1539 tf = Typeface.SANS_SERIF;
1543 tf = Typeface.SERIF;
1547 tf = Typeface.MONOSPACE;
1551 setTypeface(tf, styleIndex);
1554 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1555 boolean hasRelativeDrawables = (start != null) || (end != null);
1556 if (hasRelativeDrawables) {
1557 Drawables dr = mDrawables;
1559 mDrawables = dr = new Drawables(getContext());
1561 mDrawables.mOverride = true;
1562 final Rect compoundRect = dr.mCompoundRect;
1563 int[] state = getDrawableState();
1564 if (start != null) {
1565 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1566 start.setState(state);
1567 start.copyBounds(compoundRect);
1568 start.setCallback(this);
1570 dr.mDrawableStart = start;
1571 dr.mDrawableSizeStart = compoundRect.width();
1572 dr.mDrawableHeightStart = compoundRect.height();
1574 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1577 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1578 end.setState(state);
1579 end.copyBounds(compoundRect);
1580 end.setCallback(this);
1582 dr.mDrawableEnd = end;
1583 dr.mDrawableSizeEnd = compoundRect.width();
1584 dr.mDrawableHeightEnd = compoundRect.height();
1586 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1588 resetResolvedDrawables();
1590 applyCompoundDrawableTint();
1594 @android.view.RemotableViewMethod
1596 public void setEnabled(boolean enabled) {
1597 if (enabled == isEnabled()) {
1602 // Hide the soft input if the currently active TextView is disabled
1603 InputMethodManager imm = InputMethodManager.peekInstance();
1604 if (imm != null && imm.isActive(this)) {
1605 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1609 super.setEnabled(enabled);
1612 // Make sure IME is updated with current editor info.
1613 InputMethodManager imm = InputMethodManager.peekInstance();
1614 if (imm != null) imm.restartInput(this);
1617 // Will change text color
1618 if (mEditor != null) {
1619 mEditor.invalidateTextDisplayList();
1620 mEditor.prepareCursorControllers();
1622 // start or stop the cursor blinking as appropriate
1623 mEditor.makeBlink();
1628 * Sets the typeface and style in which the text should be displayed,
1629 * and turns on the fake bold and italic bits in the Paint if the
1630 * Typeface that you provided does not have all the bits in the
1631 * style that you specified.
1633 * @attr ref android.R.styleable#TextView_typeface
1634 * @attr ref android.R.styleable#TextView_textStyle
1636 public void setTypeface(Typeface tf, int style) {
1639 tf = Typeface.defaultFromStyle(style);
1641 tf = Typeface.create(tf, style);
1645 // now compute what (if any) algorithmic styling is needed
1646 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1647 int need = style & ~typefaceStyle;
1648 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1649 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1651 mTextPaint.setFakeBoldText(false);
1652 mTextPaint.setTextSkewX(0);
1658 * Subclasses override this to specify that they have a KeyListener
1659 * by default even if not specifically called for in the XML options.
1661 protected boolean getDefaultEditable() {
1666 * Subclasses override this to specify a default movement method.
1668 protected MovementMethod getDefaultMovementMethod() {
1673 * Return the text the TextView is displaying. If setText() was called with
1674 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1675 * the return value from this method to Spannable or Editable, respectively.
1677 * Note: The content of the return value should not be modified. If you want
1678 * a modifiable one, you should make your own copy first.
1680 * @attr ref android.R.styleable#TextView_text
1682 @ViewDebug.CapturedViewProperty
1683 public CharSequence getText() {
1688 * Returns the length, in characters, of the text managed by this TextView
1690 public int length() {
1691 return mText.length();
1695 * Return the text the TextView is displaying as an Editable object. If
1696 * the text is not editable, null is returned.
1700 public Editable getEditableText() {
1701 return (mText instanceof Editable) ? (Editable)mText : null;
1705 * @return the height of one standard line in pixels. Note that markup
1706 * within the text can cause individual lines to be taller or shorter
1707 * than this height, and the layout may contain additional first-
1708 * or last-line padding.
1710 public int getLineHeight() {
1711 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1715 * @return the Layout that is currently being used to display the text.
1716 * This can be null if the text or width has recently changes.
1718 public final Layout getLayout() {
1723 * @return the Layout that is currently being used to display the hint text.
1726 final Layout getHintLayout() {
1731 * Retrieve the {@link android.content.UndoManager} that is currently associated
1732 * with this TextView. By default there is no associated UndoManager, so null
1733 * is returned. One can be associated with the TextView through
1734 * {@link #setUndoManager(android.content.UndoManager, String)}
1738 public final UndoManager getUndoManager() {
1739 // TODO: Consider supporting a global undo manager.
1740 throw new UnsupportedOperationException("not implemented");
1748 public final Editor getEditorForTesting() {
1753 * Associate an {@link android.content.UndoManager} with this TextView. Once
1754 * done, all edit operations on the TextView will result in appropriate
1755 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1758 * @param undoManager The {@link android.content.UndoManager} to associate with
1759 * this TextView, or null to clear any existing association.
1760 * @param tag String tag identifying this particular TextView owner in the
1761 * UndoManager. This is used to keep the correct association with the
1762 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1766 public final void setUndoManager(UndoManager undoManager, String tag) {
1767 // TODO: Consider supporting a global undo manager. An implementation will need to:
1768 // * createEditorIfNeeded()
1769 // * Promote to BufferType.EDITABLE if needed.
1770 // * Update the UndoManager and UndoOwner.
1771 // Likewise it will need to be able to restore the default UndoManager.
1772 throw new UnsupportedOperationException("not implemented");
1776 * @return the current key listener for this TextView.
1777 * This will frequently be null for non-EditText TextViews.
1779 * @attr ref android.R.styleable#TextView_numeric
1780 * @attr ref android.R.styleable#TextView_digits
1781 * @attr ref android.R.styleable#TextView_phoneNumber
1782 * @attr ref android.R.styleable#TextView_inputMethod
1783 * @attr ref android.R.styleable#TextView_capitalize
1784 * @attr ref android.R.styleable#TextView_autoText
1786 public final KeyListener getKeyListener() {
1787 return mEditor == null ? null : mEditor.mKeyListener;
1791 * Sets the key listener to be used with this TextView. This can be null
1792 * to disallow user input. Note that this method has significant and
1793 * subtle interactions with soft keyboards and other input method:
1794 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1795 * for important details. Calling this method will replace the current
1796 * content type of the text view with the content type returned by the
1799 * Be warned that if you want a TextView with a key listener or movement
1800 * method not to be focusable, or if you want a TextView without a
1801 * key listener or movement method to be focusable, you must call
1802 * {@link #setFocusable} again after calling this to get the focusability
1803 * back the way you want it.
1805 * @attr ref android.R.styleable#TextView_numeric
1806 * @attr ref android.R.styleable#TextView_digits
1807 * @attr ref android.R.styleable#TextView_phoneNumber
1808 * @attr ref android.R.styleable#TextView_inputMethod
1809 * @attr ref android.R.styleable#TextView_capitalize
1810 * @attr ref android.R.styleable#TextView_autoText
1812 public void setKeyListener(KeyListener input) {
1813 setKeyListenerOnly(input);
1814 fixFocusableAndClickableSettings();
1816 if (input != null) {
1817 createEditorIfNeeded();
1819 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1820 } catch (IncompatibleClassChangeError e) {
1821 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1823 // Change inputType, without affecting transformation.
1824 // No need to applySingleLine since mSingleLine is unchanged.
1825 setInputTypeSingleLine(mSingleLine);
1827 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1830 InputMethodManager imm = InputMethodManager.peekInstance();
1831 if (imm != null) imm.restartInput(this);
1834 private void setKeyListenerOnly(KeyListener input) {
1835 if (mEditor == null && input == null) return; // null is the default value
1837 createEditorIfNeeded();
1838 if (mEditor.mKeyListener != input) {
1839 mEditor.mKeyListener = input;
1840 if (input != null && !(mText instanceof Editable)) {
1844 setFilters((Editable) mText, mFilters);
1849 * @return the movement method being used for this TextView.
1850 * This will frequently be null for non-EditText TextViews.
1852 public final MovementMethod getMovementMethod() {
1857 * Sets the movement method (arrow key handler) to be used for
1858 * this TextView. This can be null to disallow using the arrow keys
1859 * to move the cursor or scroll the view.
1861 * Be warned that if you want a TextView with a key listener or movement
1862 * method not to be focusable, or if you want a TextView without a
1863 * key listener or movement method to be focusable, you must call
1864 * {@link #setFocusable} again after calling this to get the focusability
1865 * back the way you want it.
1867 public final void setMovementMethod(MovementMethod movement) {
1868 if (mMovement != movement) {
1869 mMovement = movement;
1871 if (movement != null && !(mText instanceof Spannable)) {
1875 fixFocusableAndClickableSettings();
1877 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1879 if (mEditor != null) mEditor.prepareCursorControllers();
1883 private void fixFocusableAndClickableSettings() {
1884 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1887 setLongClickable(true);
1889 setFocusable(false);
1890 setClickable(false);
1891 setLongClickable(false);
1896 * @return the current transformation method for this TextView.
1897 * This will frequently be null except for single-line and password
1900 * @attr ref android.R.styleable#TextView_password
1901 * @attr ref android.R.styleable#TextView_singleLine
1903 public final TransformationMethod getTransformationMethod() {
1904 return mTransformation;
1908 * Sets the transformation that is applied to the text that this
1909 * TextView is displaying.
1911 * @attr ref android.R.styleable#TextView_password
1912 * @attr ref android.R.styleable#TextView_singleLine
1914 public final void setTransformationMethod(TransformationMethod method) {
1915 if (method == mTransformation) {
1916 // Avoid the setText() below if the transformation is
1920 if (mTransformation != null) {
1921 if (mText instanceof Spannable) {
1922 ((Spannable) mText).removeSpan(mTransformation);
1926 mTransformation = method;
1928 if (method instanceof TransformationMethod2) {
1929 TransformationMethod2 method2 = (TransformationMethod2) method;
1930 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1931 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1933 mAllowTransformationLengthChange = false;
1938 if (hasPasswordTransformationMethod()) {
1939 notifyViewAccessibilityStateChangedIfNeeded(
1940 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1943 // PasswordTransformationMethod always have LTR text direction heuristics returned by
1944 // getTextDirectionHeuristic, needs reset
1945 mTextDir = getTextDirectionHeuristic();
1949 * Returns the top padding of the view, plus space for the top
1952 public int getCompoundPaddingTop() {
1953 final Drawables dr = mDrawables;
1954 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
1957 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1962 * Returns the bottom padding of the view, plus space for the bottom
1965 public int getCompoundPaddingBottom() {
1966 final Drawables dr = mDrawables;
1967 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
1968 return mPaddingBottom;
1970 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1975 * Returns the left padding of the view, plus space for the left
1978 public int getCompoundPaddingLeft() {
1979 final Drawables dr = mDrawables;
1980 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
1981 return mPaddingLeft;
1983 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1988 * Returns the right padding of the view, plus space for the right
1991 public int getCompoundPaddingRight() {
1992 final Drawables dr = mDrawables;
1993 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
1994 return mPaddingRight;
1996 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
2001 * Returns the start padding of the view, plus space for the start
2004 public int getCompoundPaddingStart() {
2006 switch(getLayoutDirection()) {
2008 case LAYOUT_DIRECTION_LTR:
2009 return getCompoundPaddingLeft();
2010 case LAYOUT_DIRECTION_RTL:
2011 return getCompoundPaddingRight();
2016 * Returns the end padding of the view, plus space for the end
2019 public int getCompoundPaddingEnd() {
2021 switch(getLayoutDirection()) {
2023 case LAYOUT_DIRECTION_LTR:
2024 return getCompoundPaddingRight();
2025 case LAYOUT_DIRECTION_RTL:
2026 return getCompoundPaddingLeft();
2031 * Returns the extended top padding of the view, including both the
2032 * top Drawable if any and any extra space to keep more than maxLines
2033 * of text from showing. It is only valid to call this after measuring.
2035 public int getExtendedPaddingTop() {
2036 if (mMaxMode != LINES) {
2037 return getCompoundPaddingTop();
2040 if (mLayout == null) {
2044 if (mLayout.getLineCount() <= mMaximum) {
2045 return getCompoundPaddingTop();
2048 int top = getCompoundPaddingTop();
2049 int bottom = getCompoundPaddingBottom();
2050 int viewht = getHeight() - top - bottom;
2051 int layoutht = mLayout.getLineTop(mMaximum);
2053 if (layoutht >= viewht) {
2057 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2058 if (gravity == Gravity.TOP) {
2060 } else if (gravity == Gravity.BOTTOM) {
2061 return top + viewht - layoutht;
2062 } else { // (gravity == Gravity.CENTER_VERTICAL)
2063 return top + (viewht - layoutht) / 2;
2068 * Returns the extended bottom padding of the view, including both the
2069 * bottom Drawable if any and any extra space to keep more than maxLines
2070 * of text from showing. It is only valid to call this after measuring.
2072 public int getExtendedPaddingBottom() {
2073 if (mMaxMode != LINES) {
2074 return getCompoundPaddingBottom();
2077 if (mLayout == null) {
2081 if (mLayout.getLineCount() <= mMaximum) {
2082 return getCompoundPaddingBottom();
2085 int top = getCompoundPaddingTop();
2086 int bottom = getCompoundPaddingBottom();
2087 int viewht = getHeight() - top - bottom;
2088 int layoutht = mLayout.getLineTop(mMaximum);
2090 if (layoutht >= viewht) {
2094 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2095 if (gravity == Gravity.TOP) {
2096 return bottom + viewht - layoutht;
2097 } else if (gravity == Gravity.BOTTOM) {
2099 } else { // (gravity == Gravity.CENTER_VERTICAL)
2100 return bottom + (viewht - layoutht) / 2;
2105 * Returns the total left padding of the view, including the left
2108 public int getTotalPaddingLeft() {
2109 return getCompoundPaddingLeft();
2113 * Returns the total right padding of the view, including the right
2116 public int getTotalPaddingRight() {
2117 return getCompoundPaddingRight();
2121 * Returns the total start padding of the view, including the start
2124 public int getTotalPaddingStart() {
2125 return getCompoundPaddingStart();
2129 * Returns the total end padding of the view, including the end
2132 public int getTotalPaddingEnd() {
2133 return getCompoundPaddingEnd();
2137 * Returns the total top padding of the view, including the top
2138 * Drawable if any, the extra space to keep more than maxLines
2139 * from showing, and the vertical offset for gravity, if any.
2141 public int getTotalPaddingTop() {
2142 return getExtendedPaddingTop() + getVerticalOffset(true);
2146 * Returns the total bottom padding of the view, including the bottom
2147 * Drawable if any, the extra space to keep more than maxLines
2148 * from showing, and the vertical offset for gravity, if any.
2150 public int getTotalPaddingBottom() {
2151 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2155 * Sets the Drawables (if any) to appear to the left of, above, to the
2156 * right of, and below the text. Use {@code null} if you do not want a
2157 * Drawable there. The Drawables must already have had
2158 * {@link Drawable#setBounds} called.
2160 * Calling this method will overwrite any Drawables previously set using
2161 * {@link #setCompoundDrawablesRelative} or related methods.
2163 * @attr ref android.R.styleable#TextView_drawableLeft
2164 * @attr ref android.R.styleable#TextView_drawableTop
2165 * @attr ref android.R.styleable#TextView_drawableRight
2166 * @attr ref android.R.styleable#TextView_drawableBottom
2168 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2169 @Nullable Drawable right, @Nullable Drawable bottom) {
2170 Drawables dr = mDrawables;
2172 // We're switching to absolute, discard relative.
2174 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2175 dr.mDrawableStart = null;
2176 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2177 dr.mDrawableEnd = null;
2178 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2179 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2182 final boolean drawables = left != null || top != null || right != null || bottom != null;
2184 // Clearing drawables... can we free the data structure?
2186 if (!dr.hasMetadata()) {
2189 // We need to retain the last set padding, so just clear
2190 // out all of the fields in the existing structure.
2191 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2192 if (dr.mShowing[i] != null) {
2193 dr.mShowing[i].setCallback(null);
2195 dr.mShowing[i] = null;
2197 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2198 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2199 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2200 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2205 mDrawables = dr = new Drawables(getContext());
2208 mDrawables.mOverride = false;
2210 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2211 dr.mShowing[Drawables.LEFT].setCallback(null);
2213 dr.mShowing[Drawables.LEFT] = left;
2215 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2216 dr.mShowing[Drawables.TOP].setCallback(null);
2218 dr.mShowing[Drawables.TOP] = top;
2220 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2221 dr.mShowing[Drawables.RIGHT].setCallback(null);
2223 dr.mShowing[Drawables.RIGHT] = right;
2225 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2226 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2228 dr.mShowing[Drawables.BOTTOM] = bottom;
2230 final Rect compoundRect = dr.mCompoundRect;
2233 state = getDrawableState();
2236 left.setState(state);
2237 left.copyBounds(compoundRect);
2238 left.setCallback(this);
2239 dr.mDrawableSizeLeft = compoundRect.width();
2240 dr.mDrawableHeightLeft = compoundRect.height();
2242 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2245 if (right != null) {
2246 right.setState(state);
2247 right.copyBounds(compoundRect);
2248 right.setCallback(this);
2249 dr.mDrawableSizeRight = compoundRect.width();
2250 dr.mDrawableHeightRight = compoundRect.height();
2252 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2256 top.setState(state);
2257 top.copyBounds(compoundRect);
2258 top.setCallback(this);
2259 dr.mDrawableSizeTop = compoundRect.height();
2260 dr.mDrawableWidthTop = compoundRect.width();
2262 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2265 if (bottom != null) {
2266 bottom.setState(state);
2267 bottom.copyBounds(compoundRect);
2268 bottom.setCallback(this);
2269 dr.mDrawableSizeBottom = compoundRect.height();
2270 dr.mDrawableWidthBottom = compoundRect.width();
2272 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2276 // Save initial left/right drawables
2278 dr.mDrawableLeftInitial = left;
2279 dr.mDrawableRightInitial = right;
2282 resetResolvedDrawables();
2284 applyCompoundDrawableTint();
2290 * Sets the Drawables (if any) to appear to the left of, above, to the
2291 * right of, and below the text. Use 0 if you do not want a Drawable there.
2292 * The Drawables' bounds will be set to their intrinsic bounds.
2294 * Calling this method will overwrite any Drawables previously set using
2295 * {@link #setCompoundDrawablesRelative} or related methods.
2297 * @param left Resource identifier of the left Drawable.
2298 * @param top Resource identifier of the top Drawable.
2299 * @param right Resource identifier of the right Drawable.
2300 * @param bottom Resource identifier of the bottom Drawable.
2302 * @attr ref android.R.styleable#TextView_drawableLeft
2303 * @attr ref android.R.styleable#TextView_drawableTop
2304 * @attr ref android.R.styleable#TextView_drawableRight
2305 * @attr ref android.R.styleable#TextView_drawableBottom
2307 @android.view.RemotableViewMethod
2308 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2309 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2310 final Context context = getContext();
2311 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2312 top != 0 ? context.getDrawable(top) : null,
2313 right != 0 ? context.getDrawable(right) : null,
2314 bottom != 0 ? context.getDrawable(bottom) : null);
2318 * Sets the Drawables (if any) to appear to the left of, above, to the
2319 * right of, and below the text. Use {@code null} if you do not want a
2320 * Drawable there. The Drawables' bounds will be set to their intrinsic
2323 * Calling this method will overwrite any Drawables previously set using
2324 * {@link #setCompoundDrawablesRelative} or related methods.
2326 * @attr ref android.R.styleable#TextView_drawableLeft
2327 * @attr ref android.R.styleable#TextView_drawableTop
2328 * @attr ref android.R.styleable#TextView_drawableRight
2329 * @attr ref android.R.styleable#TextView_drawableBottom
2331 @android.view.RemotableViewMethod
2332 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2333 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2336 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2338 if (right != null) {
2339 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2342 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2344 if (bottom != null) {
2345 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2347 setCompoundDrawables(left, top, right, bottom);
2351 * Sets the Drawables (if any) to appear to the start of, above, to the end
2352 * of, and below the text. Use {@code null} if you do not want a Drawable
2353 * there. The Drawables must already have had {@link Drawable#setBounds}
2356 * Calling this method will overwrite any Drawables previously set using
2357 * {@link #setCompoundDrawables} or related methods.
2359 * @attr ref android.R.styleable#TextView_drawableStart
2360 * @attr ref android.R.styleable#TextView_drawableTop
2361 * @attr ref android.R.styleable#TextView_drawableEnd
2362 * @attr ref android.R.styleable#TextView_drawableBottom
2364 @android.view.RemotableViewMethod
2365 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2366 @Nullable Drawable end, @Nullable Drawable bottom) {
2367 Drawables dr = mDrawables;
2369 // We're switching to relative, discard absolute.
2371 if (dr.mShowing[Drawables.LEFT] != null) {
2372 dr.mShowing[Drawables.LEFT].setCallback(null);
2374 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2375 if (dr.mShowing[Drawables.RIGHT] != null) {
2376 dr.mShowing[Drawables.RIGHT].setCallback(null);
2378 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2379 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2380 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2383 final boolean drawables = start != null || top != null
2384 || end != null || bottom != null;
2387 // Clearing drawables... can we free the data structure?
2389 if (!dr.hasMetadata()) {
2392 // We need to retain the last set padding, so just clear
2393 // out all of the fields in the existing structure.
2394 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2395 dr.mDrawableStart = null;
2396 if (dr.mShowing[Drawables.TOP] != null) {
2397 dr.mShowing[Drawables.TOP].setCallback(null);
2399 dr.mShowing[Drawables.TOP] = null;
2400 if (dr.mDrawableEnd != null) {
2401 dr.mDrawableEnd.setCallback(null);
2403 dr.mDrawableEnd = null;
2404 if (dr.mShowing[Drawables.BOTTOM] != null) {
2405 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2407 dr.mShowing[Drawables.BOTTOM] = null;
2408 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2409 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2410 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2411 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2416 mDrawables = dr = new Drawables(getContext());
2419 mDrawables.mOverride = true;
2421 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2422 dr.mDrawableStart.setCallback(null);
2424 dr.mDrawableStart = start;
2426 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2427 dr.mShowing[Drawables.TOP].setCallback(null);
2429 dr.mShowing[Drawables.TOP] = top;
2431 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2432 dr.mDrawableEnd.setCallback(null);
2434 dr.mDrawableEnd = end;
2436 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2437 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2439 dr.mShowing[Drawables.BOTTOM] = bottom;
2441 final Rect compoundRect = dr.mCompoundRect;
2444 state = getDrawableState();
2446 if (start != null) {
2447 start.setState(state);
2448 start.copyBounds(compoundRect);
2449 start.setCallback(this);
2450 dr.mDrawableSizeStart = compoundRect.width();
2451 dr.mDrawableHeightStart = compoundRect.height();
2453 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2457 end.setState(state);
2458 end.copyBounds(compoundRect);
2459 end.setCallback(this);
2460 dr.mDrawableSizeEnd = compoundRect.width();
2461 dr.mDrawableHeightEnd = compoundRect.height();
2463 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2467 top.setState(state);
2468 top.copyBounds(compoundRect);
2469 top.setCallback(this);
2470 dr.mDrawableSizeTop = compoundRect.height();
2471 dr.mDrawableWidthTop = compoundRect.width();
2473 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2476 if (bottom != null) {
2477 bottom.setState(state);
2478 bottom.copyBounds(compoundRect);
2479 bottom.setCallback(this);
2480 dr.mDrawableSizeBottom = compoundRect.height();
2481 dr.mDrawableWidthBottom = compoundRect.width();
2483 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2487 resetResolvedDrawables();
2494 * Sets the Drawables (if any) to appear to the start of, above, to the end
2495 * of, and below the text. Use 0 if you do not want a Drawable there. The
2496 * Drawables' bounds will be set to their intrinsic bounds.
2498 * Calling this method will overwrite any Drawables previously set using
2499 * {@link #setCompoundDrawables} or related methods.
2501 * @param start Resource identifier of the start Drawable.
2502 * @param top Resource identifier of the top Drawable.
2503 * @param end Resource identifier of the end Drawable.
2504 * @param bottom Resource identifier of the bottom Drawable.
2506 * @attr ref android.R.styleable#TextView_drawableStart
2507 * @attr ref android.R.styleable#TextView_drawableTop
2508 * @attr ref android.R.styleable#TextView_drawableEnd
2509 * @attr ref android.R.styleable#TextView_drawableBottom
2511 @android.view.RemotableViewMethod
2512 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2513 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
2514 final Context context = getContext();
2515 setCompoundDrawablesRelativeWithIntrinsicBounds(
2516 start != 0 ? context.getDrawable(start) : null,
2517 top != 0 ? context.getDrawable(top) : null,
2518 end != 0 ? context.getDrawable(end) : null,
2519 bottom != 0 ? context.getDrawable(bottom) : null);
2523 * Sets the Drawables (if any) to appear to the start of, above, to the end
2524 * of, and below the text. Use {@code null} if you do not want a Drawable
2525 * there. The Drawables' bounds will be set to their intrinsic bounds.
2527 * Calling this method will overwrite any Drawables previously set using
2528 * {@link #setCompoundDrawables} or related methods.
2530 * @attr ref android.R.styleable#TextView_drawableStart
2531 * @attr ref android.R.styleable#TextView_drawableTop
2532 * @attr ref android.R.styleable#TextView_drawableEnd
2533 * @attr ref android.R.styleable#TextView_drawableBottom
2535 @android.view.RemotableViewMethod
2536 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2537 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2539 if (start != null) {
2540 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2543 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2546 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2548 if (bottom != null) {
2549 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2551 setCompoundDrawablesRelative(start, top, end, bottom);
2555 * Returns drawables for the left, top, right, and bottom borders.
2557 * @attr ref android.R.styleable#TextView_drawableLeft
2558 * @attr ref android.R.styleable#TextView_drawableTop
2559 * @attr ref android.R.styleable#TextView_drawableRight
2560 * @attr ref android.R.styleable#TextView_drawableBottom
2563 public Drawable[] getCompoundDrawables() {
2564 final Drawables dr = mDrawables;
2566 return dr.mShowing.clone();
2568 return new Drawable[] { null, null, null, null };
2573 * Returns drawables for the start, top, end, and bottom borders.
2575 * @attr ref android.R.styleable#TextView_drawableStart
2576 * @attr ref android.R.styleable#TextView_drawableTop
2577 * @attr ref android.R.styleable#TextView_drawableEnd
2578 * @attr ref android.R.styleable#TextView_drawableBottom
2581 public Drawable[] getCompoundDrawablesRelative() {
2582 final Drawables dr = mDrawables;
2584 return new Drawable[] {
2585 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2586 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
2589 return new Drawable[] { null, null, null, null };
2594 * Sets the size of the padding between the compound drawables and
2597 * @attr ref android.R.styleable#TextView_drawablePadding
2599 @android.view.RemotableViewMethod
2600 public void setCompoundDrawablePadding(int pad) {
2601 Drawables dr = mDrawables;
2604 dr.mDrawablePadding = pad;
2608 mDrawables = dr = new Drawables(getContext());
2610 dr.mDrawablePadding = pad;
2618 * Returns the padding between the compound drawables and the text.
2620 * @attr ref android.R.styleable#TextView_drawablePadding
2622 public int getCompoundDrawablePadding() {
2623 final Drawables dr = mDrawables;
2624 return dr != null ? dr.mDrawablePadding : 0;
2628 * Applies a tint to the compound drawables. Does not modify the
2629 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2631 * Subsequent calls to
2632 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2633 * and related methods will automatically mutate the drawables and apply
2634 * the specified tint and tint mode using
2635 * {@link Drawable#setTintList(ColorStateList)}.
2637 * @param tint the tint to apply, may be {@code null} to clear tint
2639 * @attr ref android.R.styleable#TextView_drawableTint
2640 * @see #getCompoundDrawableTintList()
2641 * @see Drawable#setTintList(ColorStateList)
2643 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2644 if (mDrawables == null) {
2645 mDrawables = new Drawables(getContext());
2647 mDrawables.mTintList = tint;
2648 mDrawables.mHasTint = true;
2650 applyCompoundDrawableTint();
2654 * @return the tint applied to the compound drawables
2655 * @attr ref android.R.styleable#TextView_drawableTint
2656 * @see #setCompoundDrawableTintList(ColorStateList)
2658 public ColorStateList getCompoundDrawableTintList() {
2659 return mDrawables != null ? mDrawables.mTintList : null;
2663 * Specifies the blending mode used to apply the tint specified by
2664 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2665 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2667 * @param tintMode the blending mode used to apply the tint, may be
2668 * {@code null} to clear tint
2669 * @attr ref android.R.styleable#TextView_drawableTintMode
2670 * @see #setCompoundDrawableTintList(ColorStateList)
2671 * @see Drawable#setTintMode(PorterDuff.Mode)
2673 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2674 if (mDrawables == null) {
2675 mDrawables = new Drawables(getContext());
2677 mDrawables.mTintMode = tintMode;
2678 mDrawables.mHasTintMode = true;
2680 applyCompoundDrawableTint();
2684 * Returns the blending mode used to apply the tint to the compound
2685 * drawables, if specified.
2687 * @return the blending mode used to apply the tint to the compound
2689 * @attr ref android.R.styleable#TextView_drawableTintMode
2690 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2692 public PorterDuff.Mode getCompoundDrawableTintMode() {
2693 return mDrawables != null ? mDrawables.mTintMode : null;
2696 private void applyCompoundDrawableTint() {
2697 if (mDrawables == null) {
2701 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2702 final ColorStateList tintList = mDrawables.mTintList;
2703 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2704 final boolean hasTint = mDrawables.mHasTint;
2705 final boolean hasTintMode = mDrawables.mHasTintMode;
2706 final int[] state = getDrawableState();
2708 for (Drawable dr : mDrawables.mShowing) {
2713 if (dr == mDrawables.mDrawableError) {
2714 // From a developer's perspective, the error drawable isn't
2715 // a compound drawable. Don't apply the generic compound
2716 // drawable tint to it.
2723 dr.setTintList(tintList);
2727 dr.setTintMode(tintMode);
2730 // The drawable (or one of its children) may not have been
2731 // stateful before applying the tint, so let's try again.
2732 if (dr.isStateful()) {
2740 public void setPadding(int left, int top, int right, int bottom) {
2741 if (left != mPaddingLeft ||
2742 right != mPaddingRight ||
2743 top != mPaddingTop ||
2744 bottom != mPaddingBottom) {
2748 // the super call will requestLayout()
2749 super.setPadding(left, top, right, bottom);
2754 public void setPaddingRelative(int start, int top, int end, int bottom) {
2755 if (start != getPaddingStart() ||
2756 end != getPaddingEnd() ||
2757 top != mPaddingTop ||
2758 bottom != mPaddingBottom) {
2762 // the super call will requestLayout()
2763 super.setPaddingRelative(start, top, end, bottom);
2768 * Gets the autolink mask of the text. See {@link
2769 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2772 * @attr ref android.R.styleable#TextView_autoLink
2774 public final int getAutoLinkMask() {
2775 return mAutoLinkMask;
2779 * Sets the text appearance from the specified style resource.
2781 * Use a framework-defined {@code TextAppearance} style like
2782 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2783 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2784 * set of attributes that can be used in a custom style.
2786 * @param resId the resource identifier of the style to apply
2787 * @attr ref android.R.styleable#TextView_textAppearance
2789 @SuppressWarnings("deprecation")
2790 public void setTextAppearance(@StyleRes int resId) {
2791 setTextAppearance(mContext, resId);
2795 * Sets the text color, size, style, hint color, and highlight color
2796 * from the specified TextAppearance resource.
2798 * @deprecated Use {@link #setTextAppearance(int)} instead.
2801 public void setTextAppearance(Context context, @StyleRes int resId) {
2802 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
2804 final int textColorHighlight = ta.getColor(
2805 R.styleable.TextAppearance_textColorHighlight, 0);
2806 if (textColorHighlight != 0) {
2807 setHighlightColor(textColorHighlight);
2810 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2811 if (textColor != null) {
2812 setTextColor(textColor);
2815 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2816 if (textSize != 0) {
2817 setRawTextSize(textSize);
2820 final ColorStateList textColorHint = ta.getColorStateList(
2821 R.styleable.TextAppearance_textColorHint);
2822 if (textColorHint != null) {
2823 setHintTextColor(textColorHint);
2826 final ColorStateList textColorLink = ta.getColorStateList(
2827 R.styleable.TextAppearance_textColorLink);
2828 if (textColorLink != null) {
2829 setLinkTextColor(textColorLink);
2832 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2833 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2834 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2835 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
2837 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2838 if (shadowColor != 0) {
2839 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2840 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2841 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2842 setShadowLayer(r, dx, dy, shadowColor);
2845 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
2846 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2849 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2850 setElegantTextHeight(ta.getBoolean(
2851 R.styleable.TextAppearance_elegantTextHeight, false));
2854 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2855 setLetterSpacing(ta.getFloat(
2856 R.styleable.TextAppearance_letterSpacing, 0));
2859 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2860 setFontFeatureSettings(ta.getString(
2861 R.styleable.TextAppearance_fontFeatureSettings));
2868 * Get the default primary {@link Locale} of the text in this TextView. This will always be
2869 * the first member of {@link #getTextLocales()}.
2870 * @return the default primary {@link Locale} of the text in this TextView.
2873 public Locale getTextLocale() {
2874 return mTextPaint.getTextLocale();
2878 * Get the default {@link LocaleList} of the text in this TextView.
2879 * @return the default {@link LocaleList} of the text in this TextView.
2881 @NonNull @Size(min=1)
2882 public LocaleList getTextLocales() {
2883 return mTextPaint.getTextLocales();
2887 * Set the default {@link LocaleList} of the text in this TextView to a one-member list
2888 * containing just the given value.
2890 * @param locale the {@link Locale} for drawing text, must not be null.
2892 * @see #setTextLocales
2894 public void setTextLocale(@NonNull Locale locale) {
2895 mLocalesChanged = true;
2896 mTextPaint.setTextLocale(locale);
2897 if (mLayout != null) {
2905 * Set the default {@link LocaleList} of the text in this TextView to the given value.
2907 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
2908 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
2909 * other aspects of text display, including line breaking.
2911 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
2913 * @see Paint#setTextLocales
2915 public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
2916 mLocalesChanged = true;
2917 mTextPaint.setTextLocales(locales);
2918 if (mLayout != null) {
2926 protected void onConfigurationChanged(Configuration newConfig) {
2927 super.onConfigurationChanged(newConfig);
2928 if (!mLocalesChanged) {
2929 mTextPaint.setTextLocales(LocaleList.getDefault());
2930 if (mLayout != null) {
2939 * @return the size (in pixels) of the default text size in this TextView.
2941 @ViewDebug.ExportedProperty(category = "text")
2942 public float getTextSize() {
2943 return mTextPaint.getTextSize();
2947 * @return the size (in scaled pixels) of thee default text size in this TextView.
2950 @ViewDebug.ExportedProperty(category = "text")
2951 public float getScaledTextSize() {
2952 return mTextPaint.getTextSize() / mTextPaint.density;
2956 @ViewDebug.ExportedProperty(category = "text", mapping = {
2957 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2958 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2959 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2960 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2962 public int getTypefaceStyle() {
2963 Typeface typeface = mTextPaint.getTypeface();
2964 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
2968 * Set the default text size to the given value, interpreted as "scaled
2969 * pixel" units. This size is adjusted based on the current density and
2970 * user font size preference.
2972 * @param size The scaled pixel size.
2974 * @attr ref android.R.styleable#TextView_textSize
2976 @android.view.RemotableViewMethod
2977 public void setTextSize(float size) {
2978 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2982 * Set the default text size to a given unit and value. See {@link
2983 * TypedValue} for the possible dimension units.
2985 * @param unit The desired dimension unit.
2986 * @param size The desired size in the given units.
2988 * @attr ref android.R.styleable#TextView_textSize
2990 public void setTextSize(int unit, float size) {
2991 Context c = getContext();
2995 r = Resources.getSystem();
2997 r = c.getResources();
2999 setRawTextSize(TypedValue.applyDimension(
3000 unit, size, r.getDisplayMetrics()));
3003 private void setRawTextSize(float size) {
3004 if (size != mTextPaint.getTextSize()) {
3005 mTextPaint.setTextSize(size);
3007 if (mLayout != null) {
3016 * @return the extent by which text is currently being stretched
3017 * horizontally. This will usually be 1.
3019 public float getTextScaleX() {
3020 return mTextPaint.getTextScaleX();
3024 * Sets the extent by which text should be stretched horizontally.
3026 * @attr ref android.R.styleable#TextView_textScaleX
3028 @android.view.RemotableViewMethod
3029 public void setTextScaleX(float size) {
3030 if (size != mTextPaint.getTextScaleX()) {
3031 mUserSetTextScaleX = true;
3032 mTextPaint.setTextScaleX(size);
3034 if (mLayout != null) {
3043 * Sets the typeface and style in which the text should be displayed.
3044 * Note that not all Typeface families actually have bold and italic
3045 * variants, so you may need to use
3046 * {@link #setTypeface(Typeface, int)} to get the appearance
3047 * that you actually want.
3049 * @see #getTypeface()
3051 * @attr ref android.R.styleable#TextView_fontFamily
3052 * @attr ref android.R.styleable#TextView_typeface
3053 * @attr ref android.R.styleable#TextView_textStyle
3055 public void setTypeface(Typeface tf) {
3056 if (mTextPaint.getTypeface() != tf) {
3057 mTextPaint.setTypeface(tf);
3059 if (mLayout != null) {
3068 * @return the current typeface and style in which the text is being
3071 * @see #setTypeface(Typeface)
3073 * @attr ref android.R.styleable#TextView_fontFamily
3074 * @attr ref android.R.styleable#TextView_typeface
3075 * @attr ref android.R.styleable#TextView_textStyle
3077 public Typeface getTypeface() {
3078 return mTextPaint.getTypeface();
3082 * Set the TextView's elegant height metrics flag. This setting selects font
3083 * variants that have not been compacted to fit Latin-based vertical
3084 * metrics, and also increases top and bottom bounds to provide more space.
3086 * @param elegant set the paint's elegant metrics flag.
3088 * @attr ref android.R.styleable#TextView_elegantTextHeight
3090 public void setElegantTextHeight(boolean elegant) {
3091 if (elegant != mTextPaint.isElegantTextHeight()) {
3092 mTextPaint.setElegantTextHeight(elegant);
3093 if (mLayout != null) {
3102 * @return the extent by which text is currently being letter-spaced.
3103 * This will normally be 0.
3105 * @see #setLetterSpacing(float)
3106 * @see Paint#setLetterSpacing
3108 public float getLetterSpacing() {
3109 return mTextPaint.getLetterSpacing();
3113 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3114 * for slight expansion will be around 0.05. Negative values tighten text.
3116 * @see #getLetterSpacing()
3117 * @see Paint#getLetterSpacing
3119 * @attr ref android.R.styleable#TextView_letterSpacing
3121 @android.view.RemotableViewMethod
3122 public void setLetterSpacing(float letterSpacing) {
3123 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3124 mTextPaint.setLetterSpacing(letterSpacing);
3126 if (mLayout != null) {
3135 * Returns the font feature settings. The format is the same as the CSS
3136 * font-feature-settings attribute:
3137 * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
3138 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
3140 * @return the currently set font feature settings. Default is null.
3142 * @see #setFontFeatureSettings(String)
3143 * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String)
3146 public String getFontFeatureSettings() {
3147 return mTextPaint.getFontFeatureSettings();
3151 * Sets the break strategy for breaking paragraphs into lines. The default value for
3152 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3153 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3154 * text "dancing" when being edited.
3156 * @attr ref android.R.styleable#TextView_breakStrategy
3157 * @see #getBreakStrategy()
3159 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3160 mBreakStrategy = breakStrategy;
3161 if (mLayout != null) {
3169 * @return the currently set break strategy.
3171 * @attr ref android.R.styleable#TextView_breakStrategy
3172 * @see #setBreakStrategy(int)
3174 @Layout.BreakStrategy
3175 public int getBreakStrategy() {
3176 return mBreakStrategy;
3180 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3181 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3183 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3184 * @see #getHyphenationFrequency()
3186 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3187 mHyphenationFrequency = hyphenationFrequency;
3188 if (mLayout != null) {
3196 * @return the currently set hyphenation frequency.
3198 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3199 * @see #setHyphenationFrequency(int)
3201 @Layout.HyphenationFrequency
3202 public int getHyphenationFrequency() {
3203 return mHyphenationFrequency;
3207 * Sets font feature settings. The format is the same as the CSS
3208 * font-feature-settings attribute:
3209 * <a href="http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings">
3210 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings</a>
3212 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3214 * @see #getFontFeatureSettings()
3215 * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings()
3217 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3219 @android.view.RemotableViewMethod
3220 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3221 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3222 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3224 if (mLayout != null) {
3234 * Sets the text color for all the states (normal, selected,
3235 * focused) to be this color.
3237 * @param color A color value in the form 0xAARRGGBB.
3238 * Do not pass a resource ID. To get a color value from a resource ID, call
3239 * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}.
3241 * @see #setTextColor(ColorStateList)
3242 * @see #getTextColors()
3244 * @attr ref android.R.styleable#TextView_textColor
3246 @android.view.RemotableViewMethod
3247 public void setTextColor(@ColorInt int color) {
3248 mTextColor = ColorStateList.valueOf(color);
3253 * Sets the text color.
3255 * @see #setTextColor(int)
3256 * @see #getTextColors()
3257 * @see #setHintTextColor(ColorStateList)
3258 * @see #setLinkTextColor(ColorStateList)
3260 * @attr ref android.R.styleable#TextView_textColor
3262 public void setTextColor(ColorStateList colors) {
3263 if (colors == null) {
3264 throw new NullPointerException();
3267 mTextColor = colors;
3272 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3274 * @see #setTextColor(ColorStateList)
3275 * @see #setTextColor(int)
3277 * @attr ref android.R.styleable#TextView_textColor
3279 public final ColorStateList getTextColors() {
3284 * <p>Return the current color selected for normal text.</p>
3286 * @return Returns the current text color.
3289 public final int getCurrentTextColor() {
3290 return mCurTextColor;
3294 * Sets the color used to display the selection highlight.
3296 * @attr ref android.R.styleable#TextView_textColorHighlight
3298 @android.view.RemotableViewMethod
3299 public void setHighlightColor(@ColorInt int color) {
3300 if (mHighlightColor != color) {
3301 mHighlightColor = color;
3307 * @return the color used to display the selection highlight
3309 * @see #setHighlightColor(int)
3311 * @attr ref android.R.styleable#TextView_textColorHighlight
3314 public int getHighlightColor() {
3315 return mHighlightColor;
3319 * Sets whether the soft input method will be made visible when this
3320 * TextView gets focused. The default is true.
3322 @android.view.RemotableViewMethod
3323 public final void setShowSoftInputOnFocus(boolean show) {
3324 createEditorIfNeeded();
3325 mEditor.mShowSoftInputOnFocus = show;
3329 * Returns whether the soft input method will be made visible when this
3330 * TextView gets focused. The default is true.
3332 public final boolean getShowSoftInputOnFocus() {
3333 // When there is no Editor, return default true value
3334 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3338 * Gives the text a shadow of the specified blur radius and color, the specified
3339 * distance from its drawn position.
3341 * The text shadow produced does not interact with the properties on view
3342 * that are responsible for real time shadows,
3343 * {@link View#getElevation() elevation} and
3344 * {@link View#getTranslationZ() translationZ}.
3346 * @see Paint#setShadowLayer(float, float, float, int)
3348 * @attr ref android.R.styleable#TextView_shadowColor
3349 * @attr ref android.R.styleable#TextView_shadowDx
3350 * @attr ref android.R.styleable#TextView_shadowDy
3351 * @attr ref android.R.styleable#TextView_shadowRadius
3353 public void setShadowLayer(float radius, float dx, float dy, int color) {
3354 mTextPaint.setShadowLayer(radius, dx, dy, color);
3356 mShadowRadius = radius;
3359 mShadowColor = color;
3361 // Will change text clip region
3362 if (mEditor != null) {
3363 mEditor.invalidateTextDisplayList();
3364 mEditor.invalidateHandlesAndActionMode();
3370 * Gets the radius of the shadow layer.
3372 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3374 * @see #setShadowLayer(float, float, float, int)
3376 * @attr ref android.R.styleable#TextView_shadowRadius
3378 public float getShadowRadius() {
3379 return mShadowRadius;
3383 * @return the horizontal offset of the shadow layer
3385 * @see #setShadowLayer(float, float, float, int)
3387 * @attr ref android.R.styleable#TextView_shadowDx
3389 public float getShadowDx() {
3394 * @return the vertical offset of the shadow layer
3396 * @see #setShadowLayer(float, float, float, int)
3398 * @attr ref android.R.styleable#TextView_shadowDy
3400 public float getShadowDy() {
3405 * @return the color of the shadow layer
3407 * @see #setShadowLayer(float, float, float, int)
3409 * @attr ref android.R.styleable#TextView_shadowColor
3412 public int getShadowColor() {
3413 return mShadowColor;
3417 * @return the base paint used for the text. Please use this only to
3418 * consult the Paint's properties and not to change them.
3420 public TextPaint getPaint() {
3425 * Sets the autolink mask of the text. See {@link
3426 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3429 * @attr ref android.R.styleable#TextView_autoLink
3431 @android.view.RemotableViewMethod
3432 public final void setAutoLinkMask(int mask) {
3433 mAutoLinkMask = mask;
3437 * Sets whether the movement method will automatically be set to
3438 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3439 * set to nonzero and links are detected in {@link #setText}.
3440 * The default is true.
3442 * @attr ref android.R.styleable#TextView_linksClickable
3444 @android.view.RemotableViewMethod
3445 public final void setLinksClickable(boolean whether) {
3446 mLinksClickable = whether;
3450 * Returns whether the movement method will automatically be set to
3451 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3452 * set to nonzero and links are detected in {@link #setText}.
3453 * The default is true.
3455 * @attr ref android.R.styleable#TextView_linksClickable
3457 public final boolean getLinksClickable() {
3458 return mLinksClickable;
3462 * Returns the list of URLSpans attached to the text
3463 * (by {@link Linkify} or otherwise) if any. You can call
3464 * {@link URLSpan#getURL} on them to find where they link to
3465 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3466 * to find the region of the text they are attached to.
3468 public URLSpan[] getUrls() {
3469 if (mText instanceof Spanned) {
3470 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3472 return new URLSpan[0];
3477 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3480 * @see #setHintTextColor(ColorStateList)
3481 * @see #getHintTextColors()
3482 * @see #setTextColor(int)
3484 * @attr ref android.R.styleable#TextView_textColorHint
3486 @android.view.RemotableViewMethod
3487 public final void setHintTextColor(@ColorInt int color) {
3488 mHintTextColor = ColorStateList.valueOf(color);
3493 * Sets the color of the hint text.
3495 * @see #getHintTextColors()
3496 * @see #setHintTextColor(int)
3497 * @see #setTextColor(ColorStateList)
3498 * @see #setLinkTextColor(ColorStateList)
3500 * @attr ref android.R.styleable#TextView_textColorHint
3502 public final void setHintTextColor(ColorStateList colors) {
3503 mHintTextColor = colors;
3508 * @return the color of the hint text, for the different states of this TextView.
3510 * @see #setHintTextColor(ColorStateList)
3511 * @see #setHintTextColor(int)
3512 * @see #setTextColor(ColorStateList)
3513 * @see #setLinkTextColor(ColorStateList)
3515 * @attr ref android.R.styleable#TextView_textColorHint
3517 public final ColorStateList getHintTextColors() {
3518 return mHintTextColor;
3522 * <p>Return the current color selected to paint the hint text.</p>
3524 * @return Returns the current hint text color.
3527 public final int getCurrentHintTextColor() {
3528 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3532 * Sets the color of links in the text.
3534 * @see #setLinkTextColor(ColorStateList)
3535 * @see #getLinkTextColors()
3537 * @attr ref android.R.styleable#TextView_textColorLink
3539 @android.view.RemotableViewMethod
3540 public final void setLinkTextColor(@ColorInt int color) {
3541 mLinkTextColor = ColorStateList.valueOf(color);
3546 * Sets the color of links in the text.
3548 * @see #setLinkTextColor(int)
3549 * @see #getLinkTextColors()
3550 * @see #setTextColor(ColorStateList)
3551 * @see #setHintTextColor(ColorStateList)
3553 * @attr ref android.R.styleable#TextView_textColorLink
3555 public final void setLinkTextColor(ColorStateList colors) {
3556 mLinkTextColor = colors;
3561 * @return the list of colors used to paint the links in the text, for the different states of
3564 * @see #setLinkTextColor(ColorStateList)
3565 * @see #setLinkTextColor(int)
3567 * @attr ref android.R.styleable#TextView_textColorLink
3569 public final ColorStateList getLinkTextColors() {
3570 return mLinkTextColor;
3574 * Sets the horizontal alignment of the text and the
3575 * vertical gravity that will be used when there is extra space
3576 * in the TextView beyond what is required for the text itself.
3578 * @see android.view.Gravity
3579 * @attr ref android.R.styleable#TextView_gravity
3581 public void setGravity(int gravity) {
3582 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3583 gravity |= Gravity.START;
3585 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3586 gravity |= Gravity.TOP;
3589 boolean newLayout = false;
3591 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3592 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3596 if (gravity != mGravity) {
3602 if (mLayout != null && newLayout) {
3603 // XXX this is heavy-handed because no actual content changes.
3604 int want = mLayout.getWidth();
3605 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3607 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3608 mRight - mLeft - getCompoundPaddingLeft() -
3609 getCompoundPaddingRight(), true);
3614 * Returns the horizontal and vertical alignment of this TextView.
3616 * @see android.view.Gravity
3617 * @attr ref android.R.styleable#TextView_gravity
3619 public int getGravity() {
3624 * @return the flags on the Paint being used to display the text.
3625 * @see Paint#getFlags
3627 public int getPaintFlags() {
3628 return mTextPaint.getFlags();
3632 * Sets flags on the Paint being used to display the text and
3633 * reflows the text if they are different from the old flags.
3634 * @see Paint#setFlags
3636 @android.view.RemotableViewMethod
3637 public void setPaintFlags(int flags) {
3638 if (mTextPaint.getFlags() != flags) {
3639 mTextPaint.setFlags(flags);
3641 if (mLayout != null) {
3650 * Sets whether the text should be allowed to be wider than the
3651 * View is. If false, it will be wrapped to the width of the View.
3653 * @attr ref android.R.styleable#TextView_scrollHorizontally
3655 public void setHorizontallyScrolling(boolean whether) {
3656 if (mHorizontallyScrolling != whether) {
3657 mHorizontallyScrolling = whether;
3659 if (mLayout != null) {
3668 * Returns whether the text is allowed to be wider than the View is.
3669 * If false, the text will be wrapped to the width of the View.
3671 * @attr ref android.R.styleable#TextView_scrollHorizontally
3674 public boolean getHorizontallyScrolling() {
3675 return mHorizontallyScrolling;
3679 * Makes the TextView at least this many lines tall.
3681 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3682 * set this value to 1.
3684 * @see #getMinLines()
3686 * @attr ref android.R.styleable#TextView_minLines
3688 @android.view.RemotableViewMethod
3689 public void setMinLines(int minlines) {
3690 mMinimum = minlines;
3698 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3699 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3701 * @see #setMinLines(int)
3703 * @attr ref android.R.styleable#TextView_minLines
3705 public int getMinLines() {
3706 return mMinMode == LINES ? mMinimum : -1;
3710 * Makes the TextView at least this many pixels tall.
3712 * Setting this value overrides any other (minimum) number of lines setting.
3714 * @attr ref android.R.styleable#TextView_minHeight
3716 @android.view.RemotableViewMethod
3717 public void setMinHeight(int minHeight) {
3718 mMinimum = minHeight;
3726 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3727 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3729 * @see #setMinHeight(int)
3731 * @attr ref android.R.styleable#TextView_minHeight
3733 public int getMinHeight() {
3734 return mMinMode == PIXELS ? mMinimum : -1;
3738 * Makes the TextView at most this many lines tall.
3740 * Setting this value overrides any other (maximum) height setting.
3742 * @attr ref android.R.styleable#TextView_maxLines
3744 @android.view.RemotableViewMethod
3745 public void setMaxLines(int maxlines) {
3746 mMaximum = maxlines;
3754 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3755 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3757 * @see #setMaxLines(int)
3759 * @attr ref android.R.styleable#TextView_maxLines
3761 public int getMaxLines() {
3762 return mMaxMode == LINES ? mMaximum : -1;
3766 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3767 * {@link #setMaxLines(int)} method.
3769 * Setting this value overrides any other (maximum) number of lines setting.
3771 * @attr ref android.R.styleable#TextView_maxHeight
3773 @android.view.RemotableViewMethod
3774 public void setMaxHeight(int maxHeight) {
3775 mMaximum = maxHeight;
3783 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3784 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3786 * @see #setMaxHeight(int)
3788 * @attr ref android.R.styleable#TextView_maxHeight
3790 public int getMaxHeight() {
3791 return mMaxMode == PIXELS ? mMaximum : -1;
3795 * Makes the TextView exactly this many lines tall.
3797 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3798 * height setting. A single line TextView will set this value to 1.
3800 * @attr ref android.R.styleable#TextView_lines
3802 @android.view.RemotableViewMethod
3803 public void setLines(int lines) {
3804 mMaximum = mMinimum = lines;
3805 mMaxMode = mMinMode = LINES;
3812 * Makes the TextView exactly this many pixels tall.
3813 * You could do the same thing by specifying this number in the
3816 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3819 * @attr ref android.R.styleable#TextView_height
3821 @android.view.RemotableViewMethod
3822 public void setHeight(int pixels) {
3823 mMaximum = mMinimum = pixels;
3824 mMaxMode = mMinMode = PIXELS;
3831 * Makes the TextView at least this many ems wide
3833 * @attr ref android.R.styleable#TextView_minEms
3835 @android.view.RemotableViewMethod
3836 public void setMinEms(int minems) {
3838 mMinWidthMode = EMS;
3845 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3846 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3848 * @see #setMinEms(int)
3851 * @attr ref android.R.styleable#TextView_minEms
3853 public int getMinEms() {
3854 return mMinWidthMode == EMS ? mMinWidth : -1;
3858 * Makes the TextView at least this many pixels wide
3860 * @attr ref android.R.styleable#TextView_minWidth
3862 @android.view.RemotableViewMethod
3863 public void setMinWidth(int minpixels) {
3864 mMinWidth = minpixels;
3865 mMinWidthMode = PIXELS;
3872 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3873 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3875 * @see #setMinWidth(int)
3876 * @see #setWidth(int)
3878 * @attr ref android.R.styleable#TextView_minWidth
3880 public int getMinWidth() {
3881 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3885 * Makes the TextView at most this many ems wide
3887 * @attr ref android.R.styleable#TextView_maxEms
3889 @android.view.RemotableViewMethod
3890 public void setMaxEms(int maxems) {
3892 mMaxWidthMode = EMS;
3899 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3900 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3902 * @see #setMaxEms(int)
3905 * @attr ref android.R.styleable#TextView_maxEms
3907 public int getMaxEms() {
3908 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3912 * Makes the TextView at most this many pixels wide
3914 * @attr ref android.R.styleable#TextView_maxWidth
3916 @android.view.RemotableViewMethod
3917 public void setMaxWidth(int maxpixels) {
3918 mMaxWidth = maxpixels;
3919 mMaxWidthMode = PIXELS;
3926 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3927 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3929 * @see #setMaxWidth(int)
3930 * @see #setWidth(int)
3932 * @attr ref android.R.styleable#TextView_maxWidth
3934 public int getMaxWidth() {
3935 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3939 * Makes the TextView exactly this many ems wide
3941 * @see #setMaxEms(int)
3942 * @see #setMinEms(int)
3946 * @attr ref android.R.styleable#TextView_ems
3948 @android.view.RemotableViewMethod
3949 public void setEms(int ems) {
3950 mMaxWidth = mMinWidth = ems;
3951 mMaxWidthMode = mMinWidthMode = EMS;
3958 * Makes the TextView exactly this many pixels wide.
3959 * You could do the same thing by specifying this number in the
3962 * @see #setMaxWidth(int)
3963 * @see #setMinWidth(int)
3964 * @see #getMinWidth()
3965 * @see #getMaxWidth()
3967 * @attr ref android.R.styleable#TextView_width
3969 @android.view.RemotableViewMethod
3970 public void setWidth(int pixels) {
3971 mMaxWidth = mMinWidth = pixels;
3972 mMaxWidthMode = mMinWidthMode = PIXELS;
3979 * Sets line spacing for this TextView. Each line will have its height
3980 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3982 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3983 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3985 public void setLineSpacing(float add, float mult) {
3986 if (mSpacingAdd != add || mSpacingMult != mult) {
3988 mSpacingMult = mult;
3990 if (mLayout != null) {
3999 * Gets the line spacing multiplier
4001 * @return the value by which each line's height is multiplied to get its actual height.
4003 * @see #setLineSpacing(float, float)
4004 * @see #getLineSpacingExtra()
4006 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
4008 public float getLineSpacingMultiplier() {
4009 return mSpacingMult;
4013 * Gets the line spacing extra space
4015 * @return the extra space that is added to the height of each lines of this TextView.
4017 * @see #setLineSpacing(float, float)
4018 * @see #getLineSpacingMultiplier()
4020 * @attr ref android.R.styleable#TextView_lineSpacingExtra
4022 public float getLineSpacingExtra() {
4027 * Convenience method: Append the specified text to the TextView's
4028 * display buffer, upgrading it to BufferType.EDITABLE if it was
4029 * not already editable.
4031 public final void append(CharSequence text) {
4032 append(text, 0, text.length());
4036 * Convenience method: Append the specified text slice to the TextView's
4037 * display buffer, upgrading it to BufferType.EDITABLE if it was
4038 * not already editable.
4040 public void append(CharSequence text, int start, int end) {
4041 if (!(mText instanceof Editable)) {
4042 setText(mText, BufferType.EDITABLE);
4045 ((Editable) mText).append(text, start, end);
4047 if (mAutoLinkMask != 0) {
4048 boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4049 // Do not change the movement method for text that support text selection as it
4050 // would prevent an arbitrary cursor displacement.
4051 if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
4052 setMovementMethod(LinkMovementMethod.getInstance());
4057 private void updateTextColors() {
4058 boolean inval = false;
4059 int color = mTextColor.getColorForState(getDrawableState(), 0);
4060 if (color != mCurTextColor) {
4061 mCurTextColor = color;
4064 if (mLinkTextColor != null) {
4065 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4066 if (color != mTextPaint.linkColor) {
4067 mTextPaint.linkColor = color;
4071 if (mHintTextColor != null) {
4072 color = mHintTextColor.getColorForState(getDrawableState(), 0);
4073 if (color != mCurHintTextColor) {
4074 mCurHintTextColor = color;
4075 if (mText.length() == 0) {
4081 // Text needs to be redrawn with the new color
4082 if (mEditor != null) mEditor.invalidateTextDisplayList();
4088 protected void drawableStateChanged() {
4089 super.drawableStateChanged();
4091 if (mTextColor != null && mTextColor.isStateful()
4092 || (mHintTextColor != null && mHintTextColor.isStateful())
4093 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4097 if (mDrawables != null) {
4098 final int[] state = getDrawableState();
4099 for (Drawable dr : mDrawables.mShowing) {
4100 if (dr != null && dr.isStateful() && dr.setState(state)) {
4101 invalidateDrawable(dr);
4108 public void drawableHotspotChanged(float x, float y) {
4109 super.drawableHotspotChanged(x, y);
4111 if (mDrawables != null) {
4112 for (Drawable dr : mDrawables.mShowing) {
4114 dr.setHotspot(x, y);
4121 public Parcelable onSaveInstanceState() {
4122 Parcelable superState = super.onSaveInstanceState();
4124 // Save state if we are forced to
4125 final boolean freezesText = getFreezesText();
4126 boolean hasSelection = false;
4130 if (mText != null) {
4131 start = getSelectionStart();
4132 end = getSelectionEnd();
4133 if (start >= 0 || end >= 0) {
4134 // Or save state if there is a selection
4135 hasSelection = true;
4139 if (freezesText || hasSelection) {
4140 SavedState ss = new SavedState(superState);
4143 if (mText instanceof Spanned) {
4144 final Spannable sp = new SpannableStringBuilder(mText);
4146 if (mEditor != null) {
4147 removeMisspelledSpans(sp);
4148 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4153 ss.text = mText.toString();
4158 // XXX Should also save the current scroll position!
4159 ss.selStart = start;
4163 if (isFocused() && start >= 0 && end >= 0) {
4164 ss.frozenWithFocus = true;
4167 ss.error = getError();
4169 if (mEditor != null) {
4170 ss.editorState = mEditor.saveInstanceState();
4178 void removeMisspelledSpans(Spannable spannable) {
4179 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4180 SuggestionSpan.class);
4181 for (int i = 0; i < suggestionSpans.length; i++) {
4182 int flags = suggestionSpans[i].getFlags();
4183 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4184 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4185 spannable.removeSpan(suggestionSpans[i]);
4191 public void onRestoreInstanceState(Parcelable state) {
4192 if (!(state instanceof SavedState)) {
4193 super.onRestoreInstanceState(state);
4197 SavedState ss = (SavedState)state;
4198 super.onRestoreInstanceState(ss.getSuperState());
4200 // XXX restore buffer type too, as well as lots of other stuff
4201 if (ss.text != null) {
4205 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4206 if (mText instanceof Spannable) {
4207 int len = mText.length();
4209 if (ss.selStart > len || ss.selEnd > len) {
4210 String restored = "";
4212 if (ss.text != null) {
4213 restored = "(restored) ";
4216 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4217 "/" + ss.selEnd + " out of range for " + restored +
4220 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4222 if (ss.frozenWithFocus) {
4223 createEditorIfNeeded();
4224 mEditor.mFrozenWithFocus = true;
4230 if (ss.error != null) {
4231 final CharSequence error = ss.error;
4232 // Display the error later, after the first layout pass
4233 post(new Runnable() {
4235 if (mEditor == null || !mEditor.mErrorWasChanged) {
4242 if (ss.editorState != null) {
4243 createEditorIfNeeded();
4244 mEditor.restoreInstanceState(ss.editorState);
4249 * Control whether this text view saves its entire text contents when
4250 * freezing to an icicle, in addition to dynamic state such as cursor
4251 * position. By default this is false, not saving the text. Set to true
4252 * if the text in the text view is not being saved somewhere else in
4253 * persistent storage (such as in a content provider) so that if the
4254 * view is later thawed the user will not lose their data. For
4255 * {@link android.widget.EditText} it is always enabled, regardless of
4256 * the value of the attribute.
4258 * @param freezesText Controls whether a frozen icicle should include the
4259 * entire text data: true to include it, false to not.
4261 * @attr ref android.R.styleable#TextView_freezesText
4263 @android.view.RemotableViewMethod
4264 public void setFreezesText(boolean freezesText) {
4265 mFreezesText = freezesText;
4269 * Return whether this text view is including its entire text contents
4270 * in frozen icicles. For {@link android.widget.EditText} it always returns true.
4272 * @return Returns true if text is included, false if it isn't.
4274 * @see #setFreezesText
4276 public boolean getFreezesText() {
4277 return mFreezesText;
4280 ///////////////////////////////////////////////////////////////////////////
4283 * Sets the Factory used to create new Editables.
4285 public final void setEditableFactory(Editable.Factory factory) {
4286 mEditableFactory = factory;
4291 * Sets the Factory used to create new Spannables.
4293 public final void setSpannableFactory(Spannable.Factory factory) {
4294 mSpannableFactory = factory;
4299 * Sets the string value of the TextView. TextView <em>does not</em> accept
4300 * HTML-like formatting, which you can do with text strings in XML resource files.
4301 * To style your strings, attach android.text.style.* objects to a
4302 * {@link android.text.SpannableString SpannableString}, or see the
4303 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4304 * Available Resource Types</a> documentation for an example of setting
4305 * formatted text in the XML resource file.
4307 * @attr ref android.R.styleable#TextView_text
4309 @android.view.RemotableViewMethod
4310 public final void setText(CharSequence text) {
4311 setText(text, mBufferType);
4315 * Like {@link #setText(CharSequence)},
4316 * except that the cursor position (if any) is retained in the new text.
4318 * @param text The new text to place in the text view.
4320 * @see #setText(CharSequence)
4322 @android.view.RemotableViewMethod
4323 public final void setTextKeepState(CharSequence text) {
4324 setTextKeepState(text, mBufferType);
4328 * Sets the text that this TextView is to display (see
4329 * {@link #setText(CharSequence)}) and also sets whether it is stored
4330 * in a styleable/spannable buffer and whether it is editable.
4332 * @attr ref android.R.styleable#TextView_text
4333 * @attr ref android.R.styleable#TextView_bufferType
4335 public void setText(CharSequence text, BufferType type) {
4336 setText(text, type, true, 0);
4338 if (mCharWrapper != null) {
4339 mCharWrapper.mChars = null;
4343 private void setText(CharSequence text, BufferType type,
4344 boolean notifyBefore, int oldlen) {
4349 // If suggestions are not enabled, remove the suggestion spans from the text
4350 if (!isSuggestionsEnabled()) {
4351 text = removeSuggestionSpans(text);
4354 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4356 if (text instanceof Spanned &&
4357 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4358 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4359 setHorizontalFadingEdgeEnabled(true);
4360 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4362 setHorizontalFadingEdgeEnabled(false);
4363 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4365 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4368 int n = mFilters.length;
4369 for (int i = 0; i < n; i++) {
4370 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4377 if (mText != null) {
4378 oldlen = mText.length();
4379 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4381 sendBeforeTextChanged("", 0, 0, text.length());
4385 boolean needEditableForNotification = false;
4387 if (mListeners != null && mListeners.size() != 0) {
4388 needEditableForNotification = true;
4391 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4392 needEditableForNotification) {
4393 createEditorIfNeeded();
4394 mEditor.forgetUndoRedo();
4395 Editable t = mEditableFactory.newEditable(text);
4397 setFilters(t, mFilters);
4398 InputMethodManager imm = InputMethodManager.peekInstance();
4399 if (imm != null) imm.restartInput(this);
4400 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4401 text = mSpannableFactory.newSpannable(text);
4402 } else if (!(text instanceof CharWrapper)) {
4403 text = TextUtils.stringOrSpannedString(text);
4406 if (mAutoLinkMask != 0) {
4409 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4410 s2 = (Spannable) text;
4412 s2 = mSpannableFactory.newSpannable(text);
4415 if (Linkify.addLinks(s2, mAutoLinkMask)) {
4417 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4420 * We must go ahead and set the text before changing the
4421 * movement method, because setMovementMethod() may call
4422 * setText() again to try to upgrade the buffer type.
4426 // Do not change the movement method for text that support text selection as it
4427 // would prevent an arbitrary cursor displacement.
4428 if (mLinksClickable && !textCanBeSelected()) {
4429 setMovementMethod(LinkMovementMethod.getInstance());
4437 if (mTransformation == null) {
4438 mTransformed = text;
4440 mTransformed = mTransformation.getTransformation(text, this);
4443 final int textLength = text.length();
4445 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4446 Spannable sp = (Spannable) text;
4448 // Remove any ChangeWatchers that might have come from other TextViews.
4449 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4450 final int count = watchers.length;
4451 for (int i = 0; i < count; i++) {
4452 sp.removeSpan(watchers[i]);
4455 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4457 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4458 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4460 if (mEditor != null) mEditor.addSpanWatchers(sp);
4462 if (mTransformation != null) {
4463 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4466 if (mMovement != null) {
4467 mMovement.initialize(this, (Spannable) text);
4470 * Initializing the movement method will have set the
4471 * selection, so reset mSelectionMoved to keep that from
4472 * interfering with the normal on-focus selection-setting.
4474 if (mEditor != null) mEditor.mSelectionMoved = false;
4478 if (mLayout != null) {
4482 sendOnTextChanged(text, 0, oldlen, textLength);
4483 onTextChanged(text, 0, oldlen, textLength);
4485 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4487 if (needEditableForNotification) {
4488 sendAfterTextChanged((Editable) text);
4491 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4492 if (mEditor != null) mEditor.prepareCursorControllers();
4496 * Sets the TextView to display the specified slice of the specified
4497 * char array. You must promise that you will not change the contents
4498 * of the array except for right before another call to setText(),
4499 * since the TextView has no way to know that the text
4500 * has changed and that it needs to invalidate and re-layout.
4502 public final void setText(char[] text, int start, int len) {
4505 if (start < 0 || len < 0 || start + len > text.length) {
4506 throw new IndexOutOfBoundsException(start + ", " + len);
4510 * We must do the before-notification here ourselves because if
4511 * the old text is a CharWrapper we destroy it before calling
4512 * into the normal path.
4514 if (mText != null) {
4515 oldlen = mText.length();
4516 sendBeforeTextChanged(mText, 0, oldlen, len);
4518 sendBeforeTextChanged("", 0, 0, len);
4521 if (mCharWrapper == null) {
4522 mCharWrapper = new CharWrapper(text, start, len);
4524 mCharWrapper.set(text, start, len);
4527 setText(mCharWrapper, mBufferType, false, oldlen);
4531 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4532 * except that the cursor position (if any) is retained in the new text.
4534 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4536 public final void setTextKeepState(CharSequence text, BufferType type) {
4537 int start = getSelectionStart();
4538 int end = getSelectionEnd();
4539 int len = text.length();
4541 setText(text, type);
4543 if (start >= 0 || end >= 0) {
4544 if (mText instanceof Spannable) {
4545 Selection.setSelection((Spannable) mText,
4546 Math.max(0, Math.min(start, len)),
4547 Math.max(0, Math.min(end, len)));
4552 @android.view.RemotableViewMethod
4553 public final void setText(@StringRes int resid) {
4554 setText(getContext().getResources().getText(resid));
4557 public final void setText(@StringRes int resid, BufferType type) {
4558 setText(getContext().getResources().getText(resid), type);
4562 * Sets the text to be displayed when the text of the TextView is empty.
4563 * Null means to use the normal empty text. The hint does not currently
4564 * participate in determining the size of the view.
4566 * @attr ref android.R.styleable#TextView_hint
4568 @android.view.RemotableViewMethod
4569 public final void setHint(CharSequence hint) {
4570 mHint = TextUtils.stringOrSpannedString(hint);
4572 if (mLayout != null) {
4576 if (mText.length() == 0) {
4580 // Invalidate display list if hint is currently used
4581 if (mEditor != null && mText.length() == 0 && mHint != null) {
4582 mEditor.invalidateTextDisplayList();
4587 * Sets the text to be displayed when the text of the TextView is empty,
4590 * @attr ref android.R.styleable#TextView_hint
4592 @android.view.RemotableViewMethod
4593 public final void setHint(@StringRes int resid) {
4594 setHint(getContext().getResources().getText(resid));
4598 * Returns the hint that is displayed when the text of the TextView
4601 * @attr ref android.R.styleable#TextView_hint
4603 @ViewDebug.CapturedViewProperty
4604 public CharSequence getHint() {
4608 boolean isSingleLine() {
4612 private static boolean isMultilineInputType(int type) {
4613 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4614 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4618 * Removes the suggestion spans.
4620 CharSequence removeSuggestionSpans(CharSequence text) {
4621 if (text instanceof Spanned) {
4622 Spannable spannable;
4623 if (text instanceof Spannable) {
4624 spannable = (Spannable) text;
4626 spannable = new SpannableString(text);
4630 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4631 for (int i = 0; i < spans.length; i++) {
4632 spannable.removeSpan(spans[i]);
4639 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4640 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4641 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4642 * then a soft keyboard will not be displayed for this text view.
4644 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4645 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4648 * @see #getInputType()
4649 * @see #setRawInputType(int)
4650 * @see android.text.InputType
4651 * @attr ref android.R.styleable#TextView_inputType
4653 public void setInputType(int type) {
4654 final boolean wasPassword = isPasswordInputType(getInputType());
4655 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4656 setInputType(type, false);
4657 final boolean isPassword = isPasswordInputType(type);
4658 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4659 boolean forceUpdate = false;
4661 setTransformationMethod(PasswordTransformationMethod.getInstance());
4662 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4663 } else if (isVisiblePassword) {
4664 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4667 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4668 } else if (wasPassword || wasVisiblePassword) {
4669 // not in password mode, clean up typeface and transformation
4670 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4671 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4676 boolean singleLine = !isMultilineInputType(type);
4678 // We need to update the single line mode if it has changed or we
4679 // were previously in password mode.
4680 if (mSingleLine != singleLine || forceUpdate) {
4681 // Change single line mode, but only change the transformation if
4682 // we are not in password mode.
4683 applySingleLine(singleLine, !isPassword, true);
4686 if (!isSuggestionsEnabled()) {
4687 mText = removeSuggestionSpans(mText);
4690 InputMethodManager imm = InputMethodManager.peekInstance();
4691 if (imm != null) imm.restartInput(this);
4695 * It would be better to rely on the input type for everything. A password inputType should have
4696 * a password transformation. We should hence use isPasswordInputType instead of this method.
4699 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4700 * would install the correct transformation).
4701 * - Refuse the installation of a non-password transformation in setTransformation if the input
4704 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4705 * is useful since it matches what the user can see (obfuscated text or not).
4707 * @return true if the current transformation method is of the password type.
4709 boolean hasPasswordTransformationMethod() {
4710 return mTransformation instanceof PasswordTransformationMethod;
4713 private static boolean isPasswordInputType(int inputType) {
4714 final int variation =
4715 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4717 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4719 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4721 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4724 private static boolean isVisiblePasswordInputType(int inputType) {
4725 final int variation =
4726 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4728 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4732 * Directly change the content type integer of the text view, without
4733 * modifying any other state.
4734 * @see #setInputType(int)
4735 * @see android.text.InputType
4736 * @attr ref android.R.styleable#TextView_inputType
4738 public void setRawInputType(int type) {
4739 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4740 createEditorIfNeeded();
4741 mEditor.mInputType = type;
4744 private void setInputType(int type, boolean direct) {
4745 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4747 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4748 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4749 TextKeyListener.Capitalize cap;
4750 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4751 cap = TextKeyListener.Capitalize.CHARACTERS;
4752 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4753 cap = TextKeyListener.Capitalize.WORDS;
4754 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4755 cap = TextKeyListener.Capitalize.SENTENCES;
4757 cap = TextKeyListener.Capitalize.NONE;
4759 input = TextKeyListener.getInstance(autotext, cap);
4760 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4761 input = DigitsKeyListener.getInstance(
4762 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4763 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4764 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4765 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4766 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4767 input = DateKeyListener.getInstance();
4769 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4770 input = TimeKeyListener.getInstance();
4773 input = DateTimeKeyListener.getInstance();
4776 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4777 input = DialerKeyListener.getInstance();
4779 input = TextKeyListener.getInstance();
4781 setRawInputType(type);
4783 createEditorIfNeeded();
4784 mEditor.mKeyListener = input;
4786 setKeyListenerOnly(input);
4791 * Get the type of the editable content.
4793 * @see #setInputType(int)
4794 * @see android.text.InputType
4796 public int getInputType() {
4797 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4801 * Change the editor type integer associated with the text view, which
4802 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4804 * @see #getImeOptions
4805 * @see android.view.inputmethod.EditorInfo
4806 * @attr ref android.R.styleable#TextView_imeOptions
4808 public void setImeOptions(int imeOptions) {
4809 createEditorIfNeeded();
4810 mEditor.createInputContentTypeIfNeeded();
4811 mEditor.mInputContentType.imeOptions = imeOptions;
4815 * Get the type of the IME editor.
4817 * @see #setImeOptions(int)
4818 * @see android.view.inputmethod.EditorInfo
4820 public int getImeOptions() {
4821 return mEditor != null && mEditor.mInputContentType != null
4822 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4826 * Change the custom IME action associated with the text view, which
4827 * will be reported to an IME with {@link EditorInfo#actionLabel}
4828 * and {@link EditorInfo#actionId} when it has focus.
4829 * @see #getImeActionLabel
4830 * @see #getImeActionId
4831 * @see android.view.inputmethod.EditorInfo
4832 * @attr ref android.R.styleable#TextView_imeActionLabel
4833 * @attr ref android.R.styleable#TextView_imeActionId
4835 public void setImeActionLabel(CharSequence label, int actionId) {
4836 createEditorIfNeeded();
4837 mEditor.createInputContentTypeIfNeeded();
4838 mEditor.mInputContentType.imeActionLabel = label;
4839 mEditor.mInputContentType.imeActionId = actionId;
4843 * Get the IME action label previous set with {@link #setImeActionLabel}.
4845 * @see #setImeActionLabel
4846 * @see android.view.inputmethod.EditorInfo
4848 public CharSequence getImeActionLabel() {
4849 return mEditor != null && mEditor.mInputContentType != null
4850 ? mEditor.mInputContentType.imeActionLabel : null;
4854 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4856 * @see #setImeActionLabel
4857 * @see android.view.inputmethod.EditorInfo
4859 public int getImeActionId() {
4860 return mEditor != null && mEditor.mInputContentType != null
4861 ? mEditor.mInputContentType.imeActionId : 0;
4865 * Set a special listener to be called when an action is performed
4866 * on the text view. This will be called when the enter key is pressed,
4867 * or when an action supplied to the IME is selected by the user. Setting
4868 * this means that the normal hard key event will not insert a newline
4869 * into the text view, even if it is multi-line; holding down the ALT
4870 * modifier will, however, allow the user to insert a newline character.
4872 public void setOnEditorActionListener(OnEditorActionListener l) {
4873 createEditorIfNeeded();
4874 mEditor.createInputContentTypeIfNeeded();
4875 mEditor.mInputContentType.onEditorActionListener = l;
4879 * Called when an attached input method calls
4880 * {@link InputConnection#performEditorAction(int)
4881 * InputConnection.performEditorAction()}
4882 * for this text view. The default implementation will call your action
4883 * listener supplied to {@link #setOnEditorActionListener}, or perform
4884 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4885 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4886 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4887 * EditorInfo.IME_ACTION_DONE}.
4889 * <p>For backwards compatibility, if no IME options have been set and the
4890 * text view would not normally advance focus on enter, then
4891 * the NEXT and DONE actions received here will be turned into an enter
4892 * key down/up pair to go through the normal key handling.
4894 * @param actionCode The code of the action being performed.
4896 * @see #setOnEditorActionListener
4898 public void onEditorAction(int actionCode) {
4899 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4901 if (ict.onEditorActionListener != null) {
4902 if (ict.onEditorActionListener.onEditorAction(this,
4903 actionCode, null)) {
4908 // This is the handling for some default action.
4909 // Note that for backwards compatibility we don't do this
4910 // default handling if explicit ime options have not been given,
4911 // instead turning this into the normal enter key codes that an
4912 // app may be expecting.
4913 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4914 View v = focusSearch(FOCUS_FORWARD);
4916 if (!v.requestFocus(FOCUS_FORWARD)) {
4917 throw new IllegalStateException("focus search returned a view " +
4918 "that wasn't able to take focus!");
4923 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4924 View v = focusSearch(FOCUS_BACKWARD);
4926 if (!v.requestFocus(FOCUS_BACKWARD)) {
4927 throw new IllegalStateException("focus search returned a view " +
4928 "that wasn't able to take focus!");
4933 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4934 InputMethodManager imm = InputMethodManager.peekInstance();
4935 if (imm != null && imm.isActive(this)) {
4936 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4942 ViewRootImpl viewRootImpl = getViewRootImpl();
4943 if (viewRootImpl != null) {
4944 long eventTime = SystemClock.uptimeMillis();
4945 viewRootImpl.dispatchKeyFromIme(
4946 new KeyEvent(eventTime, eventTime,
4947 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4948 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4949 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4950 | KeyEvent.FLAG_EDITOR_ACTION));
4951 viewRootImpl.dispatchKeyFromIme(
4952 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4953 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4954 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4955 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4956 | KeyEvent.FLAG_EDITOR_ACTION));
4961 * Set the private content type of the text, which is the
4962 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4963 * field that will be filled in when creating an input connection.
4965 * @see #getPrivateImeOptions()
4966 * @see EditorInfo#privateImeOptions
4967 * @attr ref android.R.styleable#TextView_privateImeOptions
4969 public void setPrivateImeOptions(String type) {
4970 createEditorIfNeeded();
4971 mEditor.createInputContentTypeIfNeeded();
4972 mEditor.mInputContentType.privateImeOptions = type;
4976 * Get the private type of the content.
4978 * @see #setPrivateImeOptions(String)
4979 * @see EditorInfo#privateImeOptions
4981 public String getPrivateImeOptions() {
4982 return mEditor != null && mEditor.mInputContentType != null
4983 ? mEditor.mInputContentType.privateImeOptions : null;
4987 * Set the extra input data of the text, which is the
4988 * {@link EditorInfo#extras TextBoxAttribute.extras}
4989 * Bundle that will be filled in when creating an input connection. The
4990 * given integer is the resource ID of an XML resource holding an
4991 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4993 * @see #getInputExtras(boolean)
4994 * @see EditorInfo#extras
4995 * @attr ref android.R.styleable#TextView_editorExtras
4997 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4998 createEditorIfNeeded();
4999 XmlResourceParser parser = getResources().getXml(xmlResId);
5000 mEditor.createInputContentTypeIfNeeded();
5001 mEditor.mInputContentType.extras = new Bundle();
5002 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
5006 * Retrieve the input extras currently associated with the text view, which
5007 * can be viewed as well as modified.
5009 * @param create If true, the extras will be created if they don't already
5010 * exist. Otherwise, null will be returned if none have been created.
5011 * @see #setInputExtras(int)
5012 * @see EditorInfo#extras
5013 * @attr ref android.R.styleable#TextView_editorExtras
5015 public Bundle getInputExtras(boolean create) {
5016 if (mEditor == null && !create) return null;
5017 createEditorIfNeeded();
5018 if (mEditor.mInputContentType == null) {
5019 if (!create) return null;
5020 mEditor.createInputContentTypeIfNeeded();
5022 if (mEditor.mInputContentType.extras == null) {
5023 if (!create) return null;
5024 mEditor.mInputContentType.extras = new Bundle();
5026 return mEditor.mInputContentType.extras;
5030 * Change "hint" locales associated with the text view, which will be reported to an IME with
5031 * {@link EditorInfo#hintLocales} when it has focus.
5033 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5034 * call {@link InputMethodManager#restartInput(View)}.</p>
5035 * @param hintLocales List of the languages that the user is supposed to switch to no matter
5036 * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5037 * @see #getImeHIntLocales()
5038 * @see android.view.inputmethod.EditorInfo#hintLocales
5040 public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5041 createEditorIfNeeded();
5042 mEditor.createInputContentTypeIfNeeded();
5043 mEditor.mInputContentType.imeHintLocales = hintLocales;
5047 * @return The current languages list "hint". {@code null} when no "hint" is available.
5048 * @see #setImeHintLocales(LocaleList)
5049 * @see android.view.inputmethod.EditorInfo#hintLocales
5052 public LocaleList getImeHintLocales() {
5053 if (mEditor == null) { return null; }
5054 if (mEditor.mInputContentType == null) { return null; }
5055 return mEditor.mInputContentType.imeHintLocales;
5059 * Returns the error message that was set to be displayed with
5060 * {@link #setError}, or <code>null</code> if no error was set
5061 * or if it the error was cleared by the widget after user input.
5063 public CharSequence getError() {
5064 return mEditor == null ? null : mEditor.mError;
5068 * Sets the right-hand compound drawable of the TextView to the "error"
5069 * icon and sets an error message that will be displayed in a popup when
5070 * the TextView has focus. The icon and error message will be reset to
5071 * null when any key events cause changes to the TextView's text. If the
5072 * <code>error</code> is <code>null</code>, the error message and icon
5075 @android.view.RemotableViewMethod
5076 public void setError(CharSequence error) {
5077 if (error == null) {
5078 setError(null, null);
5080 Drawable dr = getContext().getDrawable(
5081 com.android.internal.R.drawable.indicator_input_error);
5083 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5084 setError(error, dr);
5089 * Sets the right-hand compound drawable of the TextView to the specified
5090 * icon and sets an error message that will be displayed in a popup when
5091 * the TextView has focus. The icon and error message will be reset to
5092 * null when any key events cause changes to the TextView's text. The
5093 * drawable must already have had {@link Drawable#setBounds} set on it.
5094 * If the <code>error</code> is <code>null</code>, the error message will
5095 * be cleared (and you should provide a <code>null</code> icon as well).
5097 public void setError(CharSequence error, Drawable icon) {
5098 createEditorIfNeeded();
5099 mEditor.setError(error, icon);
5100 notifyViewAccessibilityStateChangedIfNeeded(
5101 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5105 protected boolean setFrame(int l, int t, int r, int b) {
5106 boolean result = super.setFrame(l, t, r, b);
5108 if (mEditor != null) mEditor.setFrame();
5110 restartMarqueeIfNeeded();
5115 private void restartMarqueeIfNeeded() {
5116 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5117 mRestartMarquee = false;
5123 * Sets the list of input filters that will be used if the buffer is
5124 * Editable. Has no effect otherwise.
5126 * @attr ref android.R.styleable#TextView_maxLength
5128 public void setFilters(InputFilter[] filters) {
5129 if (filters == null) {
5130 throw new IllegalArgumentException();
5135 if (mText instanceof Editable) {
5136 setFilters((Editable) mText, filters);
5141 * Sets the list of input filters on the specified Editable,
5142 * and includes mInput in the list if it is an InputFilter.
5144 private void setFilters(Editable e, InputFilter[] filters) {
5145 if (mEditor != null) {
5146 final boolean undoFilter = mEditor.mUndoInputFilter != null;
5147 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5149 if (undoFilter) num++;
5150 if (keyFilter) num++;
5152 InputFilter[] nf = new InputFilter[filters.length + num];
5154 System.arraycopy(filters, 0, nf, 0, filters.length);
5157 nf[filters.length] = mEditor.mUndoInputFilter;
5161 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5168 e.setFilters(filters);
5172 * Returns the current list of input filters.
5174 * @attr ref android.R.styleable#TextView_maxLength
5176 public InputFilter[] getFilters() {
5180 /////////////////////////////////////////////////////////////////////////
5182 private int getBoxHeight(Layout l) {
5183 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5184 int padding = (l == mHintLayout) ?
5185 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5186 getExtendedPaddingTop() + getExtendedPaddingBottom();
5187 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5190 int getVerticalOffset(boolean forceNormal) {
5192 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5195 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5199 if (gravity != Gravity.TOP) {
5200 int boxht = getBoxHeight(l);
5201 int textht = l.getHeight();
5203 if (textht < boxht) {
5204 if (gravity == Gravity.BOTTOM)
5205 voffset = boxht - textht;
5206 else // (gravity == Gravity.CENTER_VERTICAL)
5207 voffset = (boxht - textht) >> 1;
5213 private int getBottomVerticalOffset(boolean forceNormal) {
5215 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5218 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5222 if (gravity != Gravity.BOTTOM) {
5223 int boxht = getBoxHeight(l);
5224 int textht = l.getHeight();
5226 if (textht < boxht) {
5227 if (gravity == Gravity.TOP)
5228 voffset = boxht - textht;
5229 else // (gravity == Gravity.CENTER_VERTICAL)
5230 voffset = (boxht - textht) >> 1;
5236 void invalidateCursorPath() {
5237 if (mHighlightPathBogus) {
5240 final int horizontalPadding = getCompoundPaddingLeft();
5241 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5243 if (mEditor.mCursorCount == 0) {
5244 synchronized (TEMP_RECTF) {
5246 * The reason for this concern about the thickness of the
5247 * cursor and doing the floor/ceil on the coordinates is that
5248 * some EditTexts (notably textfields in the Browser) have
5249 * anti-aliased text where not all the characters are
5250 * necessarily at integer-multiple locations. This should
5251 * make sure the entire cursor gets invalidated instead of
5252 * sometimes missing half a pixel.
5254 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5261 // mHighlightPath is guaranteed to be non null at that point.
5262 mHighlightPath.computeBounds(TEMP_RECTF, false);
5264 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5265 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5266 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5267 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5270 for (int i = 0; i < mEditor.mCursorCount; i++) {
5271 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5272 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5273 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5279 void invalidateCursor() {
5280 int where = getSelectionEnd();
5282 invalidateCursor(where, where, where);
5285 private void invalidateCursor(int a, int b, int c) {
5286 if (a >= 0 || b >= 0 || c >= 0) {
5287 int start = Math.min(Math.min(a, b), c);
5288 int end = Math.max(Math.max(a, b), c);
5289 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5294 * Invalidates the region of text enclosed between the start and end text offsets.
5296 void invalidateRegion(int start, int end, boolean invalidateCursor) {
5297 if (mLayout == null) {
5300 int lineStart = mLayout.getLineForOffset(start);
5301 int top = mLayout.getLineTop(lineStart);
5303 // This is ridiculous, but the descent from the line above
5304 // can hang down into the line we really want to redraw,
5305 // so we have to invalidate part of the line above to make
5306 // sure everything that needs to be redrawn really is.
5307 // (But not the whole line above, because that would cause
5308 // the same problem with the descenders on the line above it!)
5309 if (lineStart > 0) {
5310 top -= mLayout.getLineDescent(lineStart - 1);
5316 lineEnd = lineStart;
5318 lineEnd = mLayout.getLineForOffset(end);
5320 int bottom = mLayout.getLineBottom(lineEnd);
5322 // mEditor can be null in case selection is set programmatically.
5323 if (invalidateCursor && mEditor != null) {
5324 for (int i = 0; i < mEditor.mCursorCount; i++) {
5325 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5326 top = Math.min(top, bounds.top);
5327 bottom = Math.max(bottom, bounds.bottom);
5331 final int compoundPaddingLeft = getCompoundPaddingLeft();
5332 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5335 if (lineStart == lineEnd && !invalidateCursor) {
5336 left = (int) mLayout.getPrimaryHorizontal(start);
5337 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5338 left += compoundPaddingLeft;
5339 right += compoundPaddingLeft;
5341 // Rectangle bounding box when the region spans several lines
5342 left = compoundPaddingLeft;
5343 right = getWidth() - getCompoundPaddingRight();
5346 invalidate(mScrollX + left, verticalPadding + top,
5347 mScrollX + right, verticalPadding + bottom);
5351 private void registerForPreDraw() {
5352 if (!mPreDrawRegistered) {
5353 getViewTreeObserver().addOnPreDrawListener(this);
5354 mPreDrawRegistered = true;
5358 private void unregisterForPreDraw() {
5359 getViewTreeObserver().removeOnPreDrawListener(this);
5360 mPreDrawRegistered = false;
5361 mPreDrawListenerDetached = false;
5367 public boolean onPreDraw() {
5368 if (mLayout == null) {
5372 if (mMovement != null) {
5373 /* This code also provides auto-scrolling when a cursor is moved using a
5374 * CursorController (insertion point or selection limits).
5375 * For selection, ensure start or end is visible depending on controller's state.
5377 int curs = getSelectionEnd();
5378 // Do not create the controller if it is not already created.
5379 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5380 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5381 curs = getSelectionStart();
5385 * TODO: This should really only keep the end in view if
5386 * it already was before the text changed. I'm not sure
5387 * of a good way to tell from here if it was.
5389 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5390 curs = mText.length();
5394 bringPointIntoView(curs);
5397 bringTextIntoView();
5400 // This has to be checked here since:
5401 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5402 // a screen rotation) since layout is not yet initialized at that point.
5403 if (mEditor != null && mEditor.mCreatedWithASelection) {
5404 mEditor.refreshTextActionMode();
5405 mEditor.mCreatedWithASelection = false;
5408 unregisterForPreDraw();
5414 protected void onAttachedToWindow() {
5415 super.onAttachedToWindow();
5417 if (mEditor != null) mEditor.onAttachedToWindow();
5419 if (mPreDrawListenerDetached) {
5420 getViewTreeObserver().addOnPreDrawListener(this);
5421 mPreDrawListenerDetached = false;
5427 protected void onDetachedFromWindowInternal() {
5428 if (mPreDrawRegistered) {
5429 getViewTreeObserver().removeOnPreDrawListener(this);
5430 mPreDrawListenerDetached = true;
5433 resetResolvedDrawables();
5435 if (mEditor != null) mEditor.onDetachedFromWindow();
5437 super.onDetachedFromWindowInternal();
5441 public void onScreenStateChanged(int screenState) {
5442 super.onScreenStateChanged(screenState);
5443 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5447 protected boolean isPaddingOffsetRequired() {
5448 return mShadowRadius != 0 || mDrawables != null;
5452 protected int getLeftPaddingOffset() {
5453 return getCompoundPaddingLeft() - mPaddingLeft +
5454 (int) Math.min(0, mShadowDx - mShadowRadius);
5458 protected int getTopPaddingOffset() {
5459 return (int) Math.min(0, mShadowDy - mShadowRadius);
5463 protected int getBottomPaddingOffset() {
5464 return (int) Math.max(0, mShadowDy + mShadowRadius);
5468 protected int getRightPaddingOffset() {
5469 return -(getCompoundPaddingRight() - mPaddingRight) +
5470 (int) Math.max(0, mShadowDx + mShadowRadius);
5474 protected boolean verifyDrawable(@NonNull Drawable who) {
5475 final boolean verified = super.verifyDrawable(who);
5476 if (!verified && mDrawables != null) {
5477 for (Drawable dr : mDrawables.mShowing) {
5487 public void jumpDrawablesToCurrentState() {
5488 super.jumpDrawablesToCurrentState();
5489 if (mDrawables != null) {
5490 for (Drawable dr : mDrawables.mShowing) {
5492 dr.jumpToCurrentState();
5499 public void invalidateDrawable(@NonNull Drawable drawable) {
5500 boolean handled = false;
5502 if (verifyDrawable(drawable)) {
5503 final Rect dirty = drawable.getBounds();
5504 int scrollX = mScrollX;
5505 int scrollY = mScrollY;
5507 // IMPORTANT: The coordinates below are based on the coordinates computed
5508 // for each compound drawable in onDraw(). Make sure to update each section
5510 final TextView.Drawables drawables = mDrawables;
5511 if (drawables != null) {
5512 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5513 final int compoundPaddingTop = getCompoundPaddingTop();
5514 final int compoundPaddingBottom = getCompoundPaddingBottom();
5515 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5517 scrollX += mPaddingLeft;
5518 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5520 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5521 final int compoundPaddingTop = getCompoundPaddingTop();
5522 final int compoundPaddingBottom = getCompoundPaddingBottom();
5523 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5525 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5526 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5528 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5529 final int compoundPaddingLeft = getCompoundPaddingLeft();
5530 final int compoundPaddingRight = getCompoundPaddingRight();
5531 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5533 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5534 scrollY += mPaddingTop;
5536 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5537 final int compoundPaddingLeft = getCompoundPaddingLeft();
5538 final int compoundPaddingRight = getCompoundPaddingRight();
5539 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5541 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5542 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5548 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5549 dirty.right + scrollX, dirty.bottom + scrollY);
5554 super.invalidateDrawable(drawable);
5559 public boolean hasOverlappingRendering() {
5560 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5561 return ((getBackground() != null && getBackground().getCurrent() != null)
5562 || mText instanceof Spannable || hasSelection()
5563 || isHorizontalFadingEdgeEnabled());
5568 * Returns the state of the {@code textIsSelectable} flag (See
5569 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5570 * to allow users to select and copy text in a non-editable TextView, the content of an
5571 * {@link EditText} can always be selected, independently of the value of this flag.
5574 * @return True if the text displayed in this TextView can be selected by the user.
5576 * @attr ref android.R.styleable#TextView_textIsSelectable
5578 public boolean isTextSelectable() {
5579 return mEditor == null ? false : mEditor.mTextIsSelectable;
5583 * Sets whether the content of this view is selectable by the user. The default is
5584 * {@code false}, meaning that the content is not selectable.
5586 * When you use a TextView to display a useful piece of information to the user (such as a
5587 * contact's address), make it selectable, so that the user can select and copy its
5588 * content. You can also use set the XML attribute
5589 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5591 * When you call this method to set the value of {@code textIsSelectable}, it sets
5592 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5593 * and {@code longClickable} to the same value. These flags correspond to the attributes
5594 * {@link android.R.styleable#View_focusable android:focusable},
5595 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5596 * {@link android.R.styleable#View_clickable android:clickable}, and
5597 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5598 * flags to a state you had set previously, call one or more of the following methods:
5599 * {@link #setFocusable(boolean) setFocusable()},
5600 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5601 * {@link #setClickable(boolean) setClickable()} or
5602 * {@link #setLongClickable(boolean) setLongClickable()}.
5604 * @param selectable Whether the content of this TextView should be selectable.
5606 public void setTextIsSelectable(boolean selectable) {
5607 if (!selectable && mEditor == null) return; // false is default value with no edit data
5609 createEditorIfNeeded();
5610 if (mEditor.mTextIsSelectable == selectable) return;
5612 mEditor.mTextIsSelectable = selectable;
5613 setFocusableInTouchMode(selectable);
5614 setFocusable(selectable);
5615 setClickable(selectable);
5616 setLongClickable(selectable);
5618 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5620 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5621 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5623 // Called by setText above, but safer in case of future code changes
5624 mEditor.prepareCursorControllers();
5628 protected int[] onCreateDrawableState(int extraSpace) {
5629 final int[] drawableState;
5632 drawableState = super.onCreateDrawableState(extraSpace);
5634 drawableState = super.onCreateDrawableState(extraSpace + 1);
5635 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5638 if (isTextSelectable()) {
5639 // Disable pressed state, which was introduced when TextView was made clickable.
5640 // Prevents text color change.
5641 // setClickable(false) would have a similar effect, but it also disables focus changes
5642 // and long press actions, which are both needed by text selection.
5643 final int length = drawableState.length;
5644 for (int i = 0; i < length; i++) {
5645 if (drawableState[i] == R.attr.state_pressed) {
5646 final int[] nonPressedState = new int[length - 1];
5647 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5648 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5649 return nonPressedState;
5654 return drawableState;
5657 private Path getUpdatedHighlightPath() {
5658 Path highlight = null;
5659 Paint highlightPaint = mHighlightPaint;
5661 final int selStart = getSelectionStart();
5662 final int selEnd = getSelectionEnd();
5663 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5664 if (selStart == selEnd) {
5665 if (mEditor != null && mEditor.isCursorVisible() &&
5666 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5667 (2 * Editor.BLINK) < Editor.BLINK) {
5668 if (mHighlightPathBogus) {
5669 if (mHighlightPath == null) mHighlightPath = new Path();
5670 mHighlightPath.reset();
5671 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5672 mEditor.updateCursorsPositions();
5673 mHighlightPathBogus = false;
5676 // XXX should pass to skin instead of drawing directly
5677 highlightPaint.setColor(mCurTextColor);
5678 highlightPaint.setStyle(Paint.Style.STROKE);
5679 highlight = mHighlightPath;
5682 if (mHighlightPathBogus) {
5683 if (mHighlightPath == null) mHighlightPath = new Path();
5684 mHighlightPath.reset();
5685 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5686 mHighlightPathBogus = false;
5689 // XXX should pass to skin instead of drawing directly
5690 highlightPaint.setColor(mHighlightColor);
5691 highlightPaint.setStyle(Paint.Style.FILL);
5693 highlight = mHighlightPath;
5702 public int getHorizontalOffsetForDrawables() {
5707 protected void onDraw(Canvas canvas) {
5708 restartMarqueeIfNeeded();
5710 // Draw the background for this view
5711 super.onDraw(canvas);
5713 final int compoundPaddingLeft = getCompoundPaddingLeft();
5714 final int compoundPaddingTop = getCompoundPaddingTop();
5715 final int compoundPaddingRight = getCompoundPaddingRight();
5716 final int compoundPaddingBottom = getCompoundPaddingBottom();
5717 final int scrollX = mScrollX;
5718 final int scrollY = mScrollY;
5719 final int right = mRight;
5720 final int left = mLeft;
5721 final int bottom = mBottom;
5722 final int top = mTop;
5723 final boolean isLayoutRtl = isLayoutRtl();
5724 final int offset = getHorizontalOffsetForDrawables();
5725 final int leftOffset = isLayoutRtl ? 0 : offset;
5726 final int rightOffset = isLayoutRtl ? offset : 0 ;
5728 final Drawables dr = mDrawables;
5731 * Compound, not extended, because the icon is not clipped
5732 * if the text height is smaller.
5735 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5736 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5738 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5739 // Make sure to update invalidateDrawable() when changing this code.
5740 if (dr.mShowing[Drawables.LEFT] != null) {
5742 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5743 scrollY + compoundPaddingTop +
5744 (vspace - dr.mDrawableHeightLeft) / 2);
5745 dr.mShowing[Drawables.LEFT].draw(canvas);
5749 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5750 // Make sure to update invalidateDrawable() when changing this code.
5751 if (dr.mShowing[Drawables.RIGHT] != null) {
5753 canvas.translate(scrollX + right - left - mPaddingRight
5754 - dr.mDrawableSizeRight - rightOffset,
5755 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5756 dr.mShowing[Drawables.RIGHT].draw(canvas);
5760 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5761 // Make sure to update invalidateDrawable() when changing this code.
5762 if (dr.mShowing[Drawables.TOP] != null) {
5764 canvas.translate(scrollX + compoundPaddingLeft +
5765 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5766 dr.mShowing[Drawables.TOP].draw(canvas);
5770 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5771 // Make sure to update invalidateDrawable() when changing this code.
5772 if (dr.mShowing[Drawables.BOTTOM] != null) {
5774 canvas.translate(scrollX + compoundPaddingLeft +
5775 (hspace - dr.mDrawableWidthBottom) / 2,
5776 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5777 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5782 int color = mCurTextColor;
5784 if (mLayout == null) {
5788 Layout layout = mLayout;
5790 if (mHint != null && mText.length() == 0) {
5791 if (mHintTextColor != null) {
5792 color = mCurHintTextColor;
5795 layout = mHintLayout;
5798 mTextPaint.setColor(color);
5799 mTextPaint.drawableState = getDrawableState();
5802 /* Would be faster if we didn't have to do this. Can we chop the
5803 (displayable) text so that we don't need to do this ever?
5806 int extendedPaddingTop = getExtendedPaddingTop();
5807 int extendedPaddingBottom = getExtendedPaddingBottom();
5809 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5810 final int maxScrollY = mLayout.getHeight() - vspace;
5812 float clipLeft = compoundPaddingLeft + scrollX;
5813 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5814 float clipRight = right - left - getCompoundPaddingRight() + scrollX;
5815 float clipBottom = bottom - top + scrollY -
5816 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5818 if (mShadowRadius != 0) {
5819 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5820 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5822 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5823 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5826 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5828 int voffsetText = 0;
5829 int voffsetCursor = 0;
5831 // translate in by our padding
5832 /* shortcircuit calling getVerticaOffset() */
5833 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5834 voffsetText = getVerticalOffset(false);
5835 voffsetCursor = getVerticalOffset(true);
5837 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5839 final int layoutDirection = getLayoutDirection();
5840 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5841 if (isMarqueeFadeEnabled()) {
5842 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5843 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5844 final int width = mRight - mLeft;
5845 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5846 final float dx = mLayout.getLineRight(0) - (width - padding);
5847 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5850 if (mMarquee != null && mMarquee.isRunning()) {
5851 final float dx = -mMarquee.getScroll();
5852 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5856 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5858 Path highlight = getUpdatedHighlightPath();
5859 if (mEditor != null) {
5860 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5862 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5865 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5866 final float dx = mMarquee.getGhostOffset();
5867 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5868 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5875 public void getFocusedRect(Rect r) {
5876 if (mLayout == null) {
5877 super.getFocusedRect(r);
5881 int selEnd = getSelectionEnd();
5883 super.getFocusedRect(r);
5887 int selStart = getSelectionStart();
5888 if (selStart < 0 || selStart >= selEnd) {
5889 int line = mLayout.getLineForOffset(selEnd);
5890 r.top = mLayout.getLineTop(line);
5891 r.bottom = mLayout.getLineBottom(line);
5892 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5893 r.right = r.left + 4;
5895 int lineStart = mLayout.getLineForOffset(selStart);
5896 int lineEnd = mLayout.getLineForOffset(selEnd);
5897 r.top = mLayout.getLineTop(lineStart);
5898 r.bottom = mLayout.getLineBottom(lineEnd);
5899 if (lineStart == lineEnd) {
5900 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5901 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5903 // Selection extends across multiple lines -- make the focused
5904 // rect cover the entire width.
5905 if (mHighlightPathBogus) {
5906 if (mHighlightPath == null) mHighlightPath = new Path();
5907 mHighlightPath.reset();
5908 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5909 mHighlightPathBogus = false;
5911 synchronized (TEMP_RECTF) {
5912 mHighlightPath.computeBounds(TEMP_RECTF, true);
5913 r.left = (int)TEMP_RECTF.left-1;
5914 r.right = (int)TEMP_RECTF.right+1;
5919 // Adjust for padding and gravity.
5920 int paddingLeft = getCompoundPaddingLeft();
5921 int paddingTop = getExtendedPaddingTop();
5922 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5923 paddingTop += getVerticalOffset(false);
5925 r.offset(paddingLeft, paddingTop);
5926 int paddingBottom = getExtendedPaddingBottom();
5927 r.bottom += paddingBottom;
5931 * Return the number of lines of text, or 0 if the internal Layout has not
5934 public int getLineCount() {
5935 return mLayout != null ? mLayout.getLineCount() : 0;
5939 * Return the baseline for the specified line (0...getLineCount() - 1)
5940 * If bounds is not null, return the top, left, right, bottom extents
5941 * of the specified line in it. If the internal Layout has not been built,
5942 * return 0 and set bounds to (0, 0, 0, 0)
5943 * @param line which line to examine (0..getLineCount() - 1)
5944 * @param bounds Optional. If not null, it returns the extent of the line
5945 * @return the Y-coordinate of the baseline
5947 public int getLineBounds(int line, Rect bounds) {
5948 if (mLayout == null) {
5949 if (bounds != null) {
5950 bounds.set(0, 0, 0, 0);
5955 int baseline = mLayout.getLineBounds(line, bounds);
5957 int voffset = getExtendedPaddingTop();
5958 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5959 voffset += getVerticalOffset(true);
5961 if (bounds != null) {
5962 bounds.offset(getCompoundPaddingLeft(), voffset);
5964 return baseline + voffset;
5969 public int getBaseline() {
5970 if (mLayout == null) {
5971 return super.getBaseline();
5974 return getBaselineOffset() + mLayout.getLineBaseline(0);
5977 int getBaselineOffset() {
5979 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5980 voffset = getVerticalOffset(true);
5983 if (isLayoutModeOptical(mParent)) {
5984 voffset -= getOpticalInsets().top;
5987 return getExtendedPaddingTop() + voffset;
5994 protected int getFadeTop(boolean offsetRequired) {
5995 if (mLayout == null) return 0;
5998 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5999 voffset = getVerticalOffset(true);
6002 if (offsetRequired) voffset += getTopPaddingOffset();
6004 return getExtendedPaddingTop() + voffset;
6011 protected int getFadeHeight(boolean offsetRequired) {
6012 return mLayout != null ? mLayout.getHeight() : 0;
6016 public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
6017 if (mText instanceof Spannable && mLinksClickable) {
6018 final float x = event.getX(pointerIndex);
6019 final float y = event.getY(pointerIndex);
6020 final int offset = getOffsetForPosition(x, y);
6021 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6022 ClickableSpan.class);
6023 if (clickables.length > 0) {
6024 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
6027 if (isTextSelectable() || isTextEditable()) {
6028 return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
6030 return super.onResolvePointerIcon(event, pointerIndex);
6034 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
6035 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6036 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6037 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6038 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6041 return super.onKeyPreIme(keyCode, event);
6047 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6048 // Do nothing unless mEditor is in text action mode.
6049 if (mEditor == null || mEditor.mTextActionMode == null) {
6053 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6054 KeyEvent.DispatcherState state = getKeyDispatcherState();
6055 if (state != null) {
6056 state.startTracking(event, this);
6059 } else if (event.getAction() == KeyEvent.ACTION_UP) {
6060 KeyEvent.DispatcherState state = getKeyDispatcherState();
6061 if (state != null) {
6062 state.handleUpEvent(event);
6064 if (event.isTracking() && !event.isCanceled()) {
6065 stopTextActionMode();
6073 public boolean onKeyDown(int keyCode, KeyEvent event) {
6074 final int which = doKeyDown(keyCode, event, null);
6075 if (which == KEY_EVENT_NOT_HANDLED) {
6076 return super.onKeyDown(keyCode, event);
6083 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
6084 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
6085 final int which = doKeyDown(keyCode, down, event);
6086 if (which == KEY_EVENT_NOT_HANDLED) {
6087 // Go through default dispatching.
6088 return super.onKeyMultiple(keyCode, repeatCount, event);
6090 if (which == KEY_EVENT_HANDLED) {
6091 // Consumed the whole thing.
6097 // We are going to dispatch the remaining events to either the input
6098 // or movement method. To do this, we will just send a repeated stream
6099 // of down and up events until we have done the complete repeatCount.
6100 // It would be nice if those interfaces had an onKeyMultiple() method,
6101 // but adding that is a more complicated change.
6102 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
6103 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
6104 // mEditor and mEditor.mInput are not null from doKeyDown
6105 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6106 while (--repeatCount > 0) {
6107 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6108 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6110 hideErrorIfUnchanged();
6112 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
6113 // mMovement is not null from doKeyDown
6114 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6115 while (--repeatCount > 0) {
6116 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6117 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6125 * Returns true if pressing ENTER in this field advances focus instead
6126 * of inserting the character. This is true mostly in single-line fields,
6127 * but also in mail addresses and subjects which will display on multiple
6128 * lines but where it doesn't make sense to insert newlines.
6130 private boolean shouldAdvanceFocusOnEnter() {
6131 if (getKeyListener() == null) {
6139 if (mEditor != null &&
6140 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6141 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6142 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6143 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
6152 * Returns true if pressing TAB in this field advances focus instead
6153 * of inserting the character. Insert tabs only in multi-line editors.
6155 private boolean shouldAdvanceFocusOnTab() {
6156 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6157 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6158 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6159 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6160 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6167 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6169 return KEY_EVENT_NOT_HANDLED;
6172 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6173 // While this shouldn't be necessary because any time we're preventing default movement we
6174 // should be restricting the focus to remain within this view, thus we'll also receive
6175 // the key up event, occasionally key up events will get dropped and we don't want to
6176 // prevent the user from traversing out of this on the next key down.
6177 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6178 mPreventDefaultMovement = false;
6182 case KeyEvent.KEYCODE_ENTER:
6183 if (event.hasNoModifiers()) {
6184 // When mInputContentType is set, we know that we are
6185 // running in a "modern" cupcake environment, so don't need
6186 // to worry about the application trying to capture
6187 // enter key events.
6188 if (mEditor != null && mEditor.mInputContentType != null) {
6189 // If there is an action listener, given them a
6190 // chance to consume the event.
6191 if (mEditor.mInputContentType.onEditorActionListener != null &&
6192 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6193 this, EditorInfo.IME_NULL, event)) {
6194 mEditor.mInputContentType.enterDown = true;
6195 // We are consuming the enter key for them.
6196 return KEY_EVENT_HANDLED;
6200 // If our editor should move focus when enter is pressed, or
6201 // this is a generated event from an IME action button, then
6202 // don't let it be inserted into the text.
6203 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6204 || shouldAdvanceFocusOnEnter()) {
6205 if (hasOnClickListeners()) {
6206 return KEY_EVENT_NOT_HANDLED;
6208 return KEY_EVENT_HANDLED;
6213 case KeyEvent.KEYCODE_DPAD_CENTER:
6214 if (event.hasNoModifiers()) {
6215 if (shouldAdvanceFocusOnEnter()) {
6216 return KEY_EVENT_NOT_HANDLED;
6221 case KeyEvent.KEYCODE_TAB:
6222 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6223 if (shouldAdvanceFocusOnTab()) {
6224 return KEY_EVENT_NOT_HANDLED;
6229 // Has to be done on key down (and not on key up) to correctly be intercepted.
6230 case KeyEvent.KEYCODE_BACK:
6231 if (mEditor != null && mEditor.mTextActionMode != null) {
6232 stopTextActionMode();
6233 return KEY_EVENT_HANDLED;
6237 case KeyEvent.KEYCODE_CUT:
6238 if (event.hasNoModifiers() && canCut()) {
6239 if (onTextContextMenuItem(ID_CUT)) {
6240 return KEY_EVENT_HANDLED;
6245 case KeyEvent.KEYCODE_COPY:
6246 if (event.hasNoModifiers() && canCopy()) {
6247 if (onTextContextMenuItem(ID_COPY)) {
6248 return KEY_EVENT_HANDLED;
6253 case KeyEvent.KEYCODE_PASTE:
6254 if (event.hasNoModifiers() && canPaste()) {
6255 if (onTextContextMenuItem(ID_PASTE)) {
6256 return KEY_EVENT_HANDLED;
6262 if (mEditor != null && mEditor.mKeyListener != null) {
6263 boolean doDown = true;
6264 if (otherEvent != null) {
6267 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6269 hideErrorIfUnchanged();
6272 return KEY_EVENT_HANDLED;
6274 } catch (AbstractMethodError e) {
6275 // onKeyOther was added after 1.0, so if it isn't
6276 // implemented we need to try to dispatch as a regular down.
6284 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6287 hideErrorIfUnchanged();
6288 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
6292 // bug 650865: sometimes we get a key event before a layout.
6293 // don't try to move around if we don't know the layout.
6295 if (mMovement != null && mLayout != null) {
6296 boolean doDown = true;
6297 if (otherEvent != null) {
6299 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6303 return KEY_EVENT_HANDLED;
6305 } catch (AbstractMethodError e) {
6306 // onKeyOther was added after 1.0, so if it isn't
6307 // implemented we need to try to dispatch as a regular down.
6311 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6312 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6313 mPreventDefaultMovement = true;
6315 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
6320 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6321 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
6325 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6329 public void resetErrorChangedFlag() {
6331 * Keep track of what the error was before doing the input
6332 * so that if an input filter changed the error, we leave
6333 * that error showing. Otherwise, we take down whatever
6334 * error was showing when the user types something.
6336 if (mEditor != null) mEditor.mErrorWasChanged = false;
6342 public void hideErrorIfUnchanged() {
6343 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6344 setError(null, null);
6349 public boolean onKeyUp(int keyCode, KeyEvent event) {
6351 return super.onKeyUp(keyCode, event);
6354 if (!KeyEvent.isModifierKey(keyCode)) {
6355 mPreventDefaultMovement = false;
6359 case KeyEvent.KEYCODE_DPAD_CENTER:
6360 if (event.hasNoModifiers()) {
6362 * If there is a click listener, just call through to
6363 * super, which will invoke it.
6365 * If there isn't a click listener, try to show the soft
6366 * input method. (It will also
6367 * call performClick(), but that won't do anything in
6370 if (!hasOnClickListeners()) {
6371 if (mMovement != null && mText instanceof Editable
6372 && mLayout != null && onCheckIsTextEditor()) {
6373 InputMethodManager imm = InputMethodManager.peekInstance();
6375 if (imm != null && getShowSoftInputOnFocus()) {
6376 imm.showSoftInput(this, 0);
6381 return super.onKeyUp(keyCode, event);
6383 case KeyEvent.KEYCODE_ENTER:
6384 if (event.hasNoModifiers()) {
6385 if (mEditor != null && mEditor.mInputContentType != null
6386 && mEditor.mInputContentType.onEditorActionListener != null
6387 && mEditor.mInputContentType.enterDown) {
6388 mEditor.mInputContentType.enterDown = false;
6389 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6390 this, EditorInfo.IME_NULL, event)) {
6395 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6396 || shouldAdvanceFocusOnEnter()) {
6398 * If there is a click listener, just call through to
6399 * super, which will invoke it.
6401 * If there isn't a click listener, try to advance focus,
6402 * but still call through to super, which will reset the
6403 * pressed state and longpress state. (It will also
6404 * call performClick(), but that won't do anything in
6407 if (!hasOnClickListeners()) {
6408 View v = focusSearch(FOCUS_DOWN);
6411 if (!v.requestFocus(FOCUS_DOWN)) {
6412 throw new IllegalStateException(
6413 "focus search returned a view " +
6414 "that wasn't able to take focus!");
6418 * Return true because we handled the key; super
6419 * will return false because there was no click
6422 super.onKeyUp(keyCode, event);
6424 } else if ((event.getFlags()
6425 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6426 // No target for next focus, but make sure the IME
6427 // if this came from it.
6428 InputMethodManager imm = InputMethodManager.peekInstance();
6429 if (imm != null && imm.isActive(this)) {
6430 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6435 return super.onKeyUp(keyCode, event);
6440 if (mEditor != null && mEditor.mKeyListener != null)
6441 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6444 if (mMovement != null && mLayout != null)
6445 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6448 return super.onKeyUp(keyCode, event);
6452 public boolean onCheckIsTextEditor() {
6453 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6457 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6458 if (onCheckIsTextEditor() && isEnabled()) {
6459 mEditor.createInputMethodStateIfNeeded();
6460 outAttrs.inputType = getInputType();
6461 if (mEditor.mInputContentType != null) {
6462 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6463 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6464 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6465 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6466 outAttrs.extras = mEditor.mInputContentType.extras;
6467 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
6469 outAttrs.imeOptions = EditorInfo.IME_NULL;
6470 outAttrs.hintLocales = null;
6472 if (focusSearch(FOCUS_DOWN) != null) {
6473 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6475 if (focusSearch(FOCUS_UP) != null) {
6476 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6478 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6479 == EditorInfo.IME_ACTION_UNSPECIFIED) {
6480 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6481 // An action has not been set, but the enter key will move to
6482 // the next focus, so set the action to that.
6483 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6485 // An action has not been set, and there is no focus to move
6486 // to, so let's just supply a "done" action.
6487 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6489 if (!shouldAdvanceFocusOnEnter()) {
6490 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6493 if (isMultilineInputType(outAttrs.inputType)) {
6494 // Multi-line text editors should always show an enter key.
6495 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6497 outAttrs.hintText = mHint;
6498 if (mText instanceof Editable) {
6499 InputConnection ic = new EditableInputConnection(this);
6500 outAttrs.initialSelStart = getSelectionStart();
6501 outAttrs.initialSelEnd = getSelectionEnd();
6502 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6510 * If this TextView contains editable content, extract a portion of it
6511 * based on the information in <var>request</var> in to <var>outText</var>.
6512 * @return Returns true if the text was successfully extracted, else false.
6514 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6515 createEditorIfNeeded();
6516 return mEditor.extractText(request, outText);
6520 * This is used to remove all style-impacting spans from text before new
6521 * extracted text is being replaced into it, so that we don't have any
6522 * lingering spans applied during the replace.
6524 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6525 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6526 int i = spans.length;
6529 spannable.removeSpan(spans[i]);
6534 * Apply to this text view the given extracted text, as previously
6535 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6537 public void setExtractedText(ExtractedText text) {
6538 Editable content = getEditableText();
6539 if (text.text != null) {
6540 if (content == null) {
6541 setText(text.text, TextView.BufferType.EDITABLE);
6544 int end = content.length();
6546 if (text.partialStartOffset >= 0) {
6547 final int N = content.length();
6548 start = text.partialStartOffset;
6549 if (start > N) start = N;
6550 end = text.partialEndOffset;
6551 if (end > N) end = N;
6554 removeParcelableSpans(content, start, end);
6555 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6556 if (text.text instanceof Spanned) {
6557 // OK to copy spans only.
6558 TextUtils.copySpansFrom((Spanned) text.text, 0, end - start,
6559 Object.class, content, start);
6562 content.replace(start, end, text.text);
6567 // Now set the selection position... make sure it is in range, to
6568 // avoid crashes. If this is a partial update, it is possible that
6569 // the underlying text may have changed, causing us problems here.
6570 // Also we just don't want to trust clients to do the right thing.
6571 Spannable sp = (Spannable)getText();
6572 final int N = sp.length();
6573 int start = text.selectionStart;
6574 if (start < 0) start = 0;
6575 else if (start > N) start = N;
6576 int end = text.selectionEnd;
6577 if (end < 0) end = 0;
6578 else if (end > N) end = N;
6579 Selection.setSelection(sp, start, end);
6581 // Finally, update the selection mode.
6582 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6583 MetaKeyKeyListener.startSelecting(this, sp);
6585 MetaKeyKeyListener.stopSelecting(this, sp);
6592 public void setExtracting(ExtractedTextRequest req) {
6593 if (mEditor.mInputMethodState != null) {
6594 mEditor.mInputMethodState.mExtractedTextRequest = req;
6596 // This would stop a possible selection mode, but no such mode is started in case
6597 // extracted mode will start. Some text is selected though, and will trigger an action mode
6598 // in the extracted view.
6599 mEditor.hideCursorAndSpanControllers();
6600 stopTextActionMode();
6601 if (mEditor.mSelectionModifierCursorController != null) {
6602 mEditor.mSelectionModifierCursorController.resetTouchOffsets();
6607 * Called by the framework in response to a text completion from
6608 * the current input method, provided by it calling
6609 * {@link InputConnection#commitCompletion
6610 * InputConnection.commitCompletion()}. The default implementation does
6611 * nothing; text views that are supporting auto-completion should override
6612 * this to do their desired behavior.
6614 * @param text The auto complete text the user has selected.
6616 public void onCommitCompletion(CompletionInfo text) {
6617 // intentionally empty
6621 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6622 * a dictionnary) from the current input method, provided by it calling
6623 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6624 * implementation flashes the background of the corrected word to provide feedback to the user.
6626 * @param info The auto correct info about the text that was corrected.
6628 public void onCommitCorrection(CorrectionInfo info) {
6629 if (mEditor != null) mEditor.onCommitCorrection(info);
6632 public void beginBatchEdit() {
6633 if (mEditor != null) mEditor.beginBatchEdit();
6636 public void endBatchEdit() {
6637 if (mEditor != null) mEditor.endBatchEdit();
6641 * Called by the framework in response to a request to begin a batch
6642 * of edit operations through a call to link {@link #beginBatchEdit()}.
6644 public void onBeginBatchEdit() {
6645 // intentionally empty
6649 * Called by the framework in response to a request to end a batch
6650 * of edit operations through a call to link {@link #endBatchEdit}.
6652 public void onEndBatchEdit() {
6653 // intentionally empty
6657 * Called by the framework in response to a private command from the
6658 * current method, provided by it calling
6659 * {@link InputConnection#performPrivateCommand
6660 * InputConnection.performPrivateCommand()}.
6662 * @param action The action name of the command.
6663 * @param data Any additional data for the command. This may be null.
6664 * @return Return true if you handled the command, else false.
6666 public boolean onPrivateIMECommand(String action, Bundle data) {
6670 private void nullLayouts() {
6671 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6672 mSavedLayout = (BoringLayout) mLayout;
6674 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6675 mSavedHintLayout = (BoringLayout) mHintLayout;
6678 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6680 mBoring = mHintBoring = null;
6682 // Since it depends on the value of mLayout
6683 if (mEditor != null) mEditor.prepareCursorControllers();
6687 * Make a new Layout based on the already-measured size of the view,
6688 * on the assumption that it was measured correctly at some point.
6690 private void assumeLayout() {
6691 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6697 int physicalWidth = width;
6699 if (mHorizontallyScrolling) {
6703 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6704 physicalWidth, false);
6707 private Layout.Alignment getLayoutAlignment() {
6708 Layout.Alignment alignment;
6709 switch (getTextAlignment()) {
6710 case TEXT_ALIGNMENT_GRAVITY:
6711 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6713 alignment = Layout.Alignment.ALIGN_NORMAL;
6716 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6719 alignment = Layout.Alignment.ALIGN_LEFT;
6722 alignment = Layout.Alignment.ALIGN_RIGHT;
6724 case Gravity.CENTER_HORIZONTAL:
6725 alignment = Layout.Alignment.ALIGN_CENTER;
6728 alignment = Layout.Alignment.ALIGN_NORMAL;
6732 case TEXT_ALIGNMENT_TEXT_START:
6733 alignment = Layout.Alignment.ALIGN_NORMAL;
6735 case TEXT_ALIGNMENT_TEXT_END:
6736 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6738 case TEXT_ALIGNMENT_CENTER:
6739 alignment = Layout.Alignment.ALIGN_CENTER;
6741 case TEXT_ALIGNMENT_VIEW_START:
6742 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6743 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6745 case TEXT_ALIGNMENT_VIEW_END:
6746 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6747 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6749 case TEXT_ALIGNMENT_INHERIT:
6750 // This should never happen as we have already resolved the text alignment
6751 // but better safe than sorry so we just fall through
6753 alignment = Layout.Alignment.ALIGN_NORMAL;
6760 * The width passed in is now the desired layout width,
6761 * not the full view width with padding.
6764 protected void makeNewLayout(int wantWidth, int hintWidth,
6765 BoringLayout.Metrics boring,
6766 BoringLayout.Metrics hintBoring,
6767 int ellipsisWidth, boolean bringIntoView) {
6770 // Update "old" cached values
6771 mOldMaximum = mMaximum;
6772 mOldMaxMode = mMaxMode;
6774 mHighlightPathBogus = true;
6776 if (wantWidth < 0) {
6779 if (hintWidth < 0) {
6783 Layout.Alignment alignment = getLayoutAlignment();
6784 final boolean testDirChange = mSingleLine && mLayout != null &&
6785 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6786 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6788 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6789 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6790 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6791 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6792 TruncateAt effectiveEllipsize = mEllipsize;
6793 if (mEllipsize == TruncateAt.MARQUEE &&
6794 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6795 effectiveEllipsize = TruncateAt.END_SMALL;
6798 if (mTextDir == null) {
6799 mTextDir = getTextDirectionHeuristic();
6802 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6803 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6804 if (switchEllipsize) {
6805 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6806 TruncateAt.END : TruncateAt.MARQUEE;
6807 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6808 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6811 shouldEllipsize = mEllipsize != null;
6814 if (mHint != null) {
6815 if (shouldEllipsize) hintWidth = wantWidth;
6817 if (hintBoring == UNKNOWN_BORING) {
6818 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6820 if (hintBoring != null) {
6821 mHintBoring = hintBoring;
6825 if (hintBoring != null) {
6826 if (hintBoring.width <= hintWidth &&
6827 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6828 if (mSavedHintLayout != null) {
6829 mHintLayout = mSavedHintLayout.
6830 replaceOrMake(mHint, mTextPaint,
6831 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6832 hintBoring, mIncludePad);
6834 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6835 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6836 hintBoring, mIncludePad);
6839 mSavedHintLayout = (BoringLayout) mHintLayout;
6840 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6841 if (mSavedHintLayout != null) {
6842 mHintLayout = mSavedHintLayout.
6843 replaceOrMake(mHint, mTextPaint,
6844 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6845 hintBoring, mIncludePad, mEllipsize,
6848 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6849 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6850 hintBoring, mIncludePad, mEllipsize,
6855 // TODO: code duplication with makeSingleLayout()
6856 if (mHintLayout == null) {
6857 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6858 mHint.length(), mTextPaint, hintWidth)
6859 .setAlignment(alignment)
6860 .setTextDirection(mTextDir)
6861 .setLineSpacing(mSpacingAdd, mSpacingMult)
6862 .setIncludePad(mIncludePad)
6863 .setBreakStrategy(mBreakStrategy)
6864 .setHyphenationFrequency(mHyphenationFrequency);
6865 if (shouldEllipsize) {
6866 builder.setEllipsize(mEllipsize)
6867 .setEllipsizedWidth(ellipsisWidth)
6868 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6870 mHintLayout = builder.build();
6874 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6875 registerForPreDraw();
6878 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6879 if (!compressText(ellipsisWidth)) {
6880 final int height = mLayoutParams.height;
6881 // If the size of the view does not depend on the size of the text, try to
6882 // start the marquee immediately
6883 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6886 // Defer the start of the marquee until we know our width (see setFrame())
6887 mRestartMarquee = true;
6892 // CursorControllers need a non-null mLayout
6893 if (mEditor != null) mEditor.prepareCursorControllers();
6899 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6900 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6902 Layout result = null;
6903 if (mText instanceof Spannable) {
6904 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6905 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6906 mBreakStrategy, mHyphenationFrequency,
6907 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6909 if (boring == UNKNOWN_BORING) {
6910 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6911 if (boring != null) {
6916 if (boring != null) {
6917 if (boring.width <= wantWidth &&
6918 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6919 if (useSaved && mSavedLayout != null) {
6920 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6921 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6922 boring, mIncludePad);
6924 result = BoringLayout.make(mTransformed, mTextPaint,
6925 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6926 boring, mIncludePad);
6930 mSavedLayout = (BoringLayout) result;
6932 } else if (shouldEllipsize && boring.width <= wantWidth) {
6933 if (useSaved && mSavedLayout != null) {
6934 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6935 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6936 boring, mIncludePad, effectiveEllipsize,
6939 result = BoringLayout.make(mTransformed, mTextPaint,
6940 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6941 boring, mIncludePad, effectiveEllipsize,
6947 if (result == null) {
6948 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6949 0, mTransformed.length(), mTextPaint, wantWidth)
6950 .setAlignment(alignment)
6951 .setTextDirection(mTextDir)
6952 .setLineSpacing(mSpacingAdd, mSpacingMult)
6953 .setIncludePad(mIncludePad)
6954 .setBreakStrategy(mBreakStrategy)
6955 .setHyphenationFrequency(mHyphenationFrequency);
6956 if (shouldEllipsize) {
6957 builder.setEllipsize(effectiveEllipsize)
6958 .setEllipsizedWidth(ellipsisWidth)
6959 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6961 // TODO: explore always setting maxLines
6962 result = builder.build();
6967 private boolean compressText(float width) {
6968 if (isHardwareAccelerated()) return false;
6970 // Only compress the text if it hasn't been compressed by the previous pass
6971 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6972 mTextPaint.getTextScaleX() == 1.0f) {
6973 final float textWidth = mLayout.getLineWidth(0);
6974 final float overflow = (textWidth + 1.0f - width) / width;
6975 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6976 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6977 post(new Runnable() {
6989 private static int desired(Layout layout) {
6990 int n = layout.getLineCount();
6991 CharSequence text = layout.getText();
6994 // if any line was wrapped, we can't use it.
6995 // but it's ok for the last line not to have a newline
6997 for (int i = 0; i < n - 1; i++) {
6998 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
7002 for (int i = 0; i < n; i++) {
7003 max = Math.max(max, layout.getLineWidth(i));
7006 return (int) Math.ceil(max);
7010 * Set whether the TextView includes extra top and bottom padding to make
7011 * room for accents that go above the normal ascent and descent.
7012 * The default is true.
7014 * @see #getIncludeFontPadding()
7016 * @attr ref android.R.styleable#TextView_includeFontPadding
7018 public void setIncludeFontPadding(boolean includepad) {
7019 if (mIncludePad != includepad) {
7020 mIncludePad = includepad;
7022 if (mLayout != null) {
7031 * Gets whether the TextView includes extra top and bottom padding to make
7032 * room for accents that go above the normal ascent and descent.
7034 * @see #setIncludeFontPadding(boolean)
7036 * @attr ref android.R.styleable#TextView_includeFontPadding
7038 public boolean getIncludeFontPadding() {
7042 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
7045 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7046 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7047 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7048 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7049 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7054 BoringLayout.Metrics boring = UNKNOWN_BORING;
7055 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7057 if (mTextDir == null) {
7058 mTextDir = getTextDirectionHeuristic();
7062 boolean fromexisting = false;
7064 if (widthMode == MeasureSpec.EXACTLY) {
7065 // Parent has told us how big to be. So be it.
7068 if (mLayout != null && mEllipsize == null) {
7069 des = desired(mLayout);
7073 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7074 if (boring != null) {
7078 fromexisting = true;
7081 if (boring == null || boring == UNKNOWN_BORING) {
7083 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
7087 width = boring.width;
7090 final Drawables dr = mDrawables;
7092 width = Math.max(width, dr.mDrawableWidthTop);
7093 width = Math.max(width, dr.mDrawableWidthBottom);
7096 if (mHint != null) {
7100 if (mHintLayout != null && mEllipsize == null) {
7101 hintDes = desired(mHintLayout);
7105 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
7106 if (hintBoring != null) {
7107 mHintBoring = hintBoring;
7111 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7113 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
7115 hintWidth = hintDes;
7117 hintWidth = hintBoring.width;
7120 if (hintWidth > width) {
7125 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7127 if (mMaxWidthMode == EMS) {
7128 width = Math.min(width, mMaxWidth * getLineHeight());
7130 width = Math.min(width, mMaxWidth);
7133 if (mMinWidthMode == EMS) {
7134 width = Math.max(width, mMinWidth * getLineHeight());
7136 width = Math.max(width, mMinWidth);
7139 // Check against our minimum width
7140 width = Math.max(width, getSuggestedMinimumWidth());
7142 if (widthMode == MeasureSpec.AT_MOST) {
7143 width = Math.min(widthSize, width);
7147 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7148 int unpaddedWidth = want;
7150 if (mHorizontallyScrolling) want = VERY_WIDE;
7152 int hintWant = want;
7153 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
7155 if (mLayout == null) {
7156 makeNewLayout(want, hintWant, boring, hintBoring,
7157 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7159 final boolean layoutChanged = (mLayout.getWidth() != want) ||
7160 (hintWidth != hintWant) ||
7161 (mLayout.getEllipsizedWidth() !=
7162 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7164 final boolean widthChanged = (mHint == null) &&
7165 (mEllipsize == null) &&
7166 (want > mLayout.getWidth()) &&
7167 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7169 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7171 if (layoutChanged || maximumChanged) {
7172 if (!maximumChanged && widthChanged) {
7173 mLayout.increaseWidthTo(want);
7175 makeNewLayout(want, hintWant, boring, hintBoring,
7176 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7179 // Nothing has changed
7183 if (heightMode == MeasureSpec.EXACTLY) {
7184 // Parent has told us how big to be. So be it.
7185 height = heightSize;
7186 mDesiredHeightAtMeasure = -1;
7188 int desired = getDesiredHeight();
7191 mDesiredHeightAtMeasure = desired;
7193 if (heightMode == MeasureSpec.AT_MOST) {
7194 height = Math.min(desired, heightSize);
7198 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
7199 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
7200 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
7204 * We didn't let makeNewLayout() register to bring the cursor into view,
7205 * so do it here if there is any possibility that it is needed.
7207 if (mMovement != null ||
7208 mLayout.getWidth() > unpaddedWidth ||
7209 mLayout.getHeight() > unpaddedHeight) {
7210 registerForPreDraw();
7215 setMeasuredDimension(width, height);
7218 private int getDesiredHeight() {
7220 getDesiredHeight(mLayout, true),
7221 getDesiredHeight(mHintLayout, mEllipsize != null));
7224 private int getDesiredHeight(Layout layout, boolean cap) {
7225 if (layout == null) {
7229 int linecount = layout.getLineCount();
7230 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7231 int desired = layout.getLineTop(linecount);
7233 final Drawables dr = mDrawables;
7235 desired = Math.max(desired, dr.mDrawableHeightLeft);
7236 desired = Math.max(desired, dr.mDrawableHeightRight);
7241 if (mMaxMode == LINES) {
7243 * Don't cap the hint to a certain number of lines.
7244 * (Do cap it, though, if we have a maximum pixel height.)
7247 if (linecount > mMaximum) {
7248 desired = layout.getLineTop(mMaximum);
7251 desired = Math.max(desired, dr.mDrawableHeightLeft);
7252 desired = Math.max(desired, dr.mDrawableHeightRight);
7256 linecount = mMaximum;
7260 desired = Math.min(desired, mMaximum);
7263 if (mMinMode == LINES) {
7264 if (linecount < mMinimum) {
7265 desired += getLineHeight() * (mMinimum - linecount);
7268 desired = Math.max(desired, mMinimum);
7271 // Check against our minimum height
7272 desired = Math.max(desired, getSuggestedMinimumHeight());
7278 * Check whether a change to the existing text layout requires a
7281 private void checkForResize() {
7282 boolean sizeChanged = false;
7284 if (mLayout != null) {
7285 // Check if our width changed
7286 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7291 // Check if our height changed
7292 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7293 int desiredHeight = getDesiredHeight();
7295 if (desiredHeight != this.getHeight()) {
7298 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7299 if (mDesiredHeightAtMeasure >= 0) {
7300 int desiredHeight = getDesiredHeight();
7302 if (desiredHeight != mDesiredHeightAtMeasure) {
7311 // caller will have already invalidated
7316 * Check whether entirely new text requires a new view layout
7317 * or merely a new text layout.
7319 private void checkForRelayout() {
7320 // If we have a fixed width, we can just swap in a new text layout
7321 // if the text height stays the same or if the view height is fixed.
7323 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7324 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7325 (mHint == null || mHintLayout != null) &&
7326 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7327 // Static width, so try making a new text layout.
7329 int oldht = mLayout.getHeight();
7330 int want = mLayout.getWidth();
7331 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7334 * No need to bring the text into view, since the size is not
7335 * changing (unless we do the requestLayout(), in which case it
7336 * will happen at measure).
7338 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7339 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7342 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7343 // In a fixed-height view, so use our new text layout.
7344 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7345 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7350 // Dynamic height, but height has stayed the same,
7351 // so use our new text layout.
7352 if (mLayout.getHeight() == oldht &&
7353 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7359 // We lose: the height has changed and we have a dynamic height.
7360 // Request a new view layout using our new text layout.
7364 // Dynamic width, so we have no choice but to request a new
7365 // view layout with a new text layout.
7373 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7374 super.onLayout(changed, left, top, right, bottom);
7375 if (mDeferScroll >= 0) {
7376 int curs = mDeferScroll;
7378 bringPointIntoView(Math.min(curs, mText.length()));
7382 private boolean isShowingHint() {
7383 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7387 * Returns true if anything changed.
7389 private boolean bringTextIntoView() {
7390 Layout layout = isShowingHint() ? mHintLayout : mLayout;
7392 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7393 line = layout.getLineCount() - 1;
7396 Layout.Alignment a = layout.getParagraphAlignment(line);
7397 int dir = layout.getParagraphDirection(line);
7398 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7399 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7400 int ht = layout.getHeight();
7402 int scrollx, scrolly;
7404 // Convert to left, center, or right alignment.
7405 if (a == Layout.Alignment.ALIGN_NORMAL) {
7406 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7407 Layout.Alignment.ALIGN_RIGHT;
7408 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7409 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7410 Layout.Alignment.ALIGN_LEFT;
7413 if (a == Layout.Alignment.ALIGN_CENTER) {
7415 * Keep centered if possible, or, if it is too wide to fit,
7416 * keep leading edge in view.
7419 int left = (int) Math.floor(layout.getLineLeft(line));
7420 int right = (int) Math.ceil(layout.getLineRight(line));
7422 if (right - left < hspace) {
7423 scrollx = (right + left) / 2 - hspace / 2;
7426 scrollx = right - hspace;
7431 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7432 int right = (int) Math.ceil(layout.getLineRight(line));
7433 scrollx = right - hspace;
7434 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7435 scrollx = (int) Math.floor(layout.getLineLeft(line));
7441 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7442 scrolly = ht - vspace;
7448 if (scrollx != mScrollX || scrolly != mScrollY) {
7449 scrollTo(scrollx, scrolly);
7457 * Move the point, specified by the offset, into the view if it is needed.
7458 * This has to be called after layout. Returns true if anything changed.
7460 public boolean bringPointIntoView(int offset) {
7461 if (isLayoutRequested()) {
7462 mDeferScroll = offset;
7465 boolean changed = false;
7467 Layout layout = isShowingHint() ? mHintLayout: mLayout;
7469 if (layout == null) return changed;
7471 int line = layout.getLineForOffset(offset);
7475 switch (layout.getParagraphAlignment(line)) {
7483 grav = layout.getParagraphDirection(line);
7485 case ALIGN_OPPOSITE:
7486 grav = -layout.getParagraphDirection(line);
7494 // We only want to clamp the cursor to fit within the layout width
7495 // in left-to-right modes, because in a right to left alignment,
7496 // we want to scroll to keep the line-right on the screen, as other
7497 // lines are likely to have text flush with the right margin, which
7498 // we want to keep visible.
7499 // A better long-term solution would probably be to measure both
7500 // the full line and a blank-trimmed version, and, for example, use
7501 // the latter measurement for centering and right alignment, but for
7502 // the time being we only implement the cursor clamping in left to
7503 // right where it is most likely to be annoying.
7504 final boolean clamped = grav > 0;
7505 // FIXME: Is it okay to truncate this, or should we round?
7506 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7507 final int top = layout.getLineTop(line);
7508 final int bottom = layout.getLineTop(line + 1);
7510 int left = (int) Math.floor(layout.getLineLeft(line));
7511 int right = (int) Math.ceil(layout.getLineRight(line));
7512 int ht = layout.getHeight();
7514 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7515 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7516 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7517 // If cursor has been clamped, make sure we don't scroll.
7518 right = Math.max(x, left + hspace);
7521 int hslack = (bottom - top) / 2;
7522 int vslack = hslack;
7524 if (vslack > vspace / 4)
7525 vslack = vspace / 4;
7526 if (hslack > hspace / 4)
7527 hslack = hspace / 4;
7532 if (top - vs < vslack)
7534 if (bottom - vs > vspace - vslack)
7535 vs = bottom - (vspace - vslack);
7536 if (ht - vs < vspace)
7542 if (x - hs < hslack) {
7545 if (x - hs > hspace - hslack) {
7546 hs = x - (hspace - hslack);
7553 if (right - hs < hspace)
7554 hs = right - hspace;
7555 } else if (grav > 0) {
7556 if (right - hs < hspace)
7557 hs = right - hspace;
7560 } else /* grav == 0 */ {
7561 if (right - left <= hspace) {
7563 * If the entire text fits, center it exactly.
7565 hs = left - (hspace - (right - left)) / 2;
7566 } else if (x > right - hslack) {
7568 * If we are near the right edge, keep the right edge
7569 * at the edge of the view.
7571 hs = right - hspace;
7572 } else if (x < left + hslack) {
7574 * If we are near the left edge, keep the left edge
7575 * at the edge of the view.
7578 } else if (left > hs) {
7580 * Is there whitespace visible at the left? Fix it if so.
7583 } else if (right < hs + hspace) {
7585 * Is there whitespace visible at the right? Fix it if so.
7587 hs = right - hspace;
7590 * Otherwise, float as needed.
7592 if (x - hs < hslack) {
7595 if (x - hs > hspace - hslack) {
7596 hs = x - (hspace - hslack);
7601 if (hs != mScrollX || vs != mScrollY) {
7602 if (mScroller == null) {
7605 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7606 int dx = hs - mScrollX;
7607 int dy = vs - mScrollY;
7609 if (duration > ANIMATED_SCROLL_GAP) {
7610 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7611 awakenScrollBars(mScroller.getDuration());
7614 if (!mScroller.isFinished()) {
7615 mScroller.abortAnimation();
7621 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7628 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7629 // requestRectangleOnScreen() is in terms of content coordinates.
7631 // The offsets here are to ensure the rectangle we are using is
7632 // within our view bounds, in case the cursor is on the far left
7633 // or right. If it isn't withing the bounds, then this request
7635 if (mTempRect == null) mTempRect = new Rect();
7636 mTempRect.set(x - 2, top, x + 2, bottom);
7637 getInterestingRect(mTempRect, line);
7638 mTempRect.offset(mScrollX, mScrollY);
7640 if (requestRectangleOnScreen(mTempRect)) {
7649 * Move the cursor, if needed, so that it is at an offset that is visible
7650 * to the user. This will not move the cursor if it represents more than
7651 * one character (a selection range). This will only work if the
7652 * TextView contains spannable text; otherwise it will do nothing.
7654 * @return True if the cursor was actually moved, false otherwise.
7656 public boolean moveCursorToVisibleOffset() {
7657 if (!(mText instanceof Spannable)) {
7660 int start = getSelectionStart();
7661 int end = getSelectionEnd();
7666 // First: make sure the line is visible on screen:
7668 int line = mLayout.getLineForOffset(start);
7670 final int top = mLayout.getLineTop(line);
7671 final int bottom = mLayout.getLineTop(line + 1);
7672 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7673 int vslack = (bottom - top) / 2;
7674 if (vslack > vspace / 4)
7675 vslack = vspace / 4;
7676 final int vs = mScrollY;
7678 if (top < (vs+vslack)) {
7679 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7680 } else if (bottom > (vspace+vs-vslack)) {
7681 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7684 // Next: make sure the character is visible on screen:
7686 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7687 final int hs = mScrollX;
7688 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7689 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7691 // line might contain bidirectional text
7692 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7693 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7695 int newStart = start;
7696 if (newStart < lowChar) {
7698 } else if (newStart > highChar) {
7699 newStart = highChar;
7702 if (newStart != start) {
7703 Selection.setSelection((Spannable)mText, newStart);
7711 public void computeScroll() {
7712 if (mScroller != null) {
7713 if (mScroller.computeScrollOffset()) {
7714 mScrollX = mScroller.getCurrX();
7715 mScrollY = mScroller.getCurrY();
7716 invalidateParentCaches();
7717 postInvalidate(); // So we draw again
7722 private void getInterestingRect(Rect r, int line) {
7723 convertFromViewportToContentCoordinates(r);
7725 // Rectangle can can be expanded on first and last line to take
7726 // padding into account.
7727 // TODO Take left/right padding into account too?
7728 if (line == 0) r.top -= getExtendedPaddingTop();
7729 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7732 private void convertFromViewportToContentCoordinates(Rect r) {
7733 final int horizontalOffset = viewportToContentHorizontalOffset();
7734 r.left += horizontalOffset;
7735 r.right += horizontalOffset;
7737 final int verticalOffset = viewportToContentVerticalOffset();
7738 r.top += verticalOffset;
7739 r.bottom += verticalOffset;
7742 int viewportToContentHorizontalOffset() {
7743 return getCompoundPaddingLeft() - mScrollX;
7746 int viewportToContentVerticalOffset() {
7747 int offset = getExtendedPaddingTop() - mScrollY;
7748 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7749 offset += getVerticalOffset(false);
7755 public void debug(int depth) {
7758 String output = debugIndent(depth);
7759 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7760 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7763 if (mText != null) {
7765 output += "mText=\"" + mText + "\" ";
7766 if (mLayout != null) {
7767 output += "mLayout width=" + mLayout.getWidth()
7768 + " height=" + mLayout.getHeight();
7771 output += "mText=NULL";
7773 Log.d(VIEW_LOG_TAG, output);
7777 * Convenience for {@link Selection#getSelectionStart}.
7779 @ViewDebug.ExportedProperty(category = "text")
7780 public int getSelectionStart() {
7781 return Selection.getSelectionStart(getText());
7785 * Convenience for {@link Selection#getSelectionEnd}.
7787 @ViewDebug.ExportedProperty(category = "text")
7788 public int getSelectionEnd() {
7789 return Selection.getSelectionEnd(getText());
7793 * Return true iff there is a selection inside this text view.
7795 public boolean hasSelection() {
7796 final int selectionStart = getSelectionStart();
7797 final int selectionEnd = getSelectionEnd();
7799 return selectionStart >= 0 && selectionStart != selectionEnd;
7802 String getSelectedText() {
7803 if (!hasSelection()) {
7807 final int start = getSelectionStart();
7808 final int end = getSelectionEnd();
7809 return String.valueOf(
7810 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
7814 * Sets the properties of this field (lines, horizontally scrolling,
7815 * transformation method) to be for a single-line input.
7817 * @attr ref android.R.styleable#TextView_singleLine
7819 public void setSingleLine() {
7820 setSingleLine(true);
7824 * Sets the properties of this field to transform input to ALL CAPS
7825 * display. This may use a "small caps" formatting if available.
7826 * This setting will be ignored if this field is editable or selectable.
7828 * This call replaces the current transformation method. Disabling this
7829 * will not necessarily restore the previous behavior from before this
7832 * @see #setTransformationMethod(TransformationMethod)
7833 * @attr ref android.R.styleable#TextView_textAllCaps
7835 public void setAllCaps(boolean allCaps) {
7837 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7839 setTransformationMethod(null);
7844 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7845 * transformation method) to be for a single-line input; if false, restores these to the default
7848 * Note that the default conditions are not necessarily those that were in effect prior this
7849 * method, and you may want to reset these properties to your custom values.
7851 * @attr ref android.R.styleable#TextView_singleLine
7853 @android.view.RemotableViewMethod
7854 public void setSingleLine(boolean singleLine) {
7855 // Could be used, but may break backward compatibility.
7856 // if (mSingleLine == singleLine) return;
7857 setInputTypeSingleLine(singleLine);
7858 applySingleLine(singleLine, true, true);
7862 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7865 private void setInputTypeSingleLine(boolean singleLine) {
7866 if (mEditor != null &&
7867 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7869 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7871 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7876 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7877 boolean changeMaxLines) {
7878 mSingleLine = singleLine;
7881 setHorizontallyScrolling(true);
7882 if (applyTransformation) {
7883 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7886 if (changeMaxLines) {
7887 setMaxLines(Integer.MAX_VALUE);
7889 setHorizontallyScrolling(false);
7890 if (applyTransformation) {
7891 setTransformationMethod(null);
7897 * Causes words in the text that are longer than the view's width
7898 * to be ellipsized instead of broken in the middle. You may also
7899 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7900 * to constrain the text to a single line. Use <code>null</code>
7901 * to turn off ellipsizing.
7903 * If {@link #setMaxLines} has been used to set two or more lines,
7904 * only {@link android.text.TextUtils.TruncateAt#END} and
7905 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7906 * (other ellipsizing types will not do anything).
7908 * @attr ref android.R.styleable#TextView_ellipsize
7910 public void setEllipsize(TextUtils.TruncateAt where) {
7911 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7912 if (mEllipsize != where) {
7915 if (mLayout != null) {
7924 * Sets how many times to repeat the marquee animation. Only applied if the
7925 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7927 * @see #getMarqueeRepeatLimit()
7929 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7931 public void setMarqueeRepeatLimit(int marqueeLimit) {
7932 mMarqueeRepeatLimit = marqueeLimit;
7936 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7937 * TextView has marquee enabled.
7939 * @return the number of times the marquee animation is repeated. -1 if the animation
7940 * repeats indefinitely
7942 * @see #setMarqueeRepeatLimit(int)
7944 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7946 public int getMarqueeRepeatLimit() {
7947 return mMarqueeRepeatLimit;
7951 * Returns where, if anywhere, words that are longer than the view
7952 * is wide should be ellipsized.
7954 @ViewDebug.ExportedProperty
7955 public TextUtils.TruncateAt getEllipsize() {
7960 * Set the TextView so that when it takes focus, all the text is
7963 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7965 @android.view.RemotableViewMethod
7966 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7967 createEditorIfNeeded();
7968 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7970 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7971 setText(mText, BufferType.SPANNABLE);
7976 * Set whether the cursor is visible. The default is true. Note that this property only
7977 * makes sense for editable TextView.
7979 * @see #isCursorVisible()
7981 * @attr ref android.R.styleable#TextView_cursorVisible
7983 @android.view.RemotableViewMethod
7984 public void setCursorVisible(boolean visible) {
7985 if (visible && mEditor == null) return; // visible is the default value with no edit data
7986 createEditorIfNeeded();
7987 if (mEditor.mCursorVisible != visible) {
7988 mEditor.mCursorVisible = visible;
7991 mEditor.makeBlink();
7993 // InsertionPointCursorController depends on mCursorVisible
7994 mEditor.prepareCursorControllers();
7999 * @return whether or not the cursor is visible (assuming this TextView is editable)
8001 * @see #setCursorVisible(boolean)
8003 * @attr ref android.R.styleable#TextView_cursorVisible
8005 public boolean isCursorVisible() {
8006 // true is the default value
8007 return mEditor == null ? true : mEditor.mCursorVisible;
8010 private boolean canMarquee() {
8011 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
8012 return width > 0 && (mLayout.getLineWidth(0) > width ||
8013 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
8014 mSavedMarqueeModeLayout.getLineWidth(0) > width));
8017 private void startMarquee() {
8018 // Do not ellipsize EditText
8019 if (getKeyListener() != null) return;
8021 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8025 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
8026 getLineCount() == 1 && canMarquee()) {
8028 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8029 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8030 final Layout tmp = mLayout;
8031 mLayout = mSavedMarqueeModeLayout;
8032 mSavedMarqueeModeLayout = tmp;
8033 setHorizontalFadingEdgeEnabled(true);
8038 if (mMarquee == null) mMarquee = new Marquee(this);
8039 mMarquee.start(mMarqueeRepeatLimit);
8043 private void stopMarquee() {
8044 if (mMarquee != null && !mMarquee.isStopped()) {
8048 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8049 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8050 final Layout tmp = mSavedMarqueeModeLayout;
8051 mSavedMarqueeModeLayout = mLayout;
8053 setHorizontalFadingEdgeEnabled(false);
8059 private void startStopMarquee(boolean start) {
8060 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8070 * This method is called when the text is changed, in case any subclasses
8071 * would like to know.
8073 * Within <code>text</code>, the <code>lengthAfter</code> characters
8074 * beginning at <code>start</code> have just replaced old text that had
8075 * length <code>lengthBefore</code>. It is an error to attempt to make
8076 * changes to <code>text</code> from this callback.
8078 * @param text The text the TextView is displaying
8079 * @param start The offset of the start of the range of the text that was
8081 * @param lengthBefore The length of the former text that has been replaced
8082 * @param lengthAfter The length of the replacement modified text
8084 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
8085 // intentionally empty, template pattern method can be overridden by subclasses
8089 * This method is called when the selection has changed, in case any
8090 * subclasses would like to know.
8092 * @param selStart The new selection start location.
8093 * @param selEnd The new selection end location.
8095 protected void onSelectionChanged(int selStart, int selEnd) {
8096 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
8100 * Adds a TextWatcher to the list of those whose methods are called
8101 * whenever this TextView's text changes.
8103 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8104 * not called after {@link #setText} calls. Now, doing {@link #setText}
8105 * if there are any text changed listeners forces the buffer type to
8106 * Editable if it would not otherwise be and does call this method.
8108 public void addTextChangedListener(TextWatcher watcher) {
8109 if (mListeners == null) {
8110 mListeners = new ArrayList<TextWatcher>();
8113 mListeners.add(watcher);
8117 * Removes the specified TextWatcher from the list of those whose
8118 * methods are called
8119 * whenever this TextView's text changes.
8121 public void removeTextChangedListener(TextWatcher watcher) {
8122 if (mListeners != null) {
8123 int i = mListeners.indexOf(watcher);
8126 mListeners.remove(i);
8131 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
8132 if (mListeners != null) {
8133 final ArrayList<TextWatcher> list = mListeners;
8134 final int count = list.size();
8135 for (int i = 0; i < count; i++) {
8136 list.get(i).beforeTextChanged(text, start, before, after);
8140 // The spans that are inside or intersect the modified region no longer make sense
8141 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8142 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
8145 // Removes all spans that are inside or actually overlap the start..end range
8146 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
8147 if (!(mText instanceof Editable)) return;
8148 Editable text = (Editable) mText;
8150 T[] spans = text.getSpans(start, end, type);
8151 final int length = spans.length;
8152 for (int i = 0; i < length; i++) {
8153 final int spanStart = text.getSpanStart(spans[i]);
8154 final int spanEnd = text.getSpanEnd(spans[i]);
8155 if (spanEnd == start || spanStart == end) break;
8156 text.removeSpan(spans[i]);
8160 void removeAdjacentSuggestionSpans(final int pos) {
8161 if (!(mText instanceof Editable)) return;
8162 final Editable text = (Editable) mText;
8164 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8165 final int length = spans.length;
8166 for (int i = 0; i < length; i++) {
8167 final int spanStart = text.getSpanStart(spans[i]);
8168 final int spanEnd = text.getSpanEnd(spans[i]);
8169 if (spanEnd == pos || spanStart == pos) {
8170 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8171 text.removeSpan(spans[i]);
8178 * Not private so it can be called from an inner class without going
8181 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
8182 if (mListeners != null) {
8183 final ArrayList<TextWatcher> list = mListeners;
8184 final int count = list.size();
8185 for (int i = 0; i < count; i++) {
8186 list.get(i).onTextChanged(text, start, before, after);
8190 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
8194 * Not private so it can be called from an inner class without going
8197 void sendAfterTextChanged(Editable text) {
8198 if (mListeners != null) {
8199 final ArrayList<TextWatcher> list = mListeners;
8200 final int count = list.size();
8201 for (int i = 0; i < count; i++) {
8202 list.get(i).afterTextChanged(text);
8205 hideErrorIfUnchanged();
8208 void updateAfterEdit() {
8210 int curs = getSelectionStart();
8212 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8213 registerForPreDraw();
8219 mHighlightPathBogus = true;
8220 if (mEditor != null) mEditor.makeBlink();
8221 bringPointIntoView(curs);
8226 * Not private so it can be called from an inner class without going
8229 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8230 sLastCutCopyOrTextChangedTime = 0;
8232 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8233 if (ims == null || ims.mBatchEditNesting == 0) {
8237 ims.mContentChanged = true;
8238 if (ims.mChangedStart < 0) {
8239 ims.mChangedStart = start;
8240 ims.mChangedEnd = start+before;
8242 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8243 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8245 ims.mChangedDelta += after-before;
8247 resetErrorChangedFlag();
8248 sendOnTextChanged(buffer, start, before, after);
8249 onTextChanged(buffer, start, before, after);
8253 * Not private so it can be called from an inner class without going
8256 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8257 // XXX Make the start and end move together if this ends up
8258 // spending too much time invalidating.
8260 boolean selChanged = false;
8261 int newSelStart=-1, newSelEnd=-1;
8263 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8265 if (what == Selection.SELECTION_END) {
8267 newSelEnd = newStart;
8269 if (oldStart >= 0 || newStart >= 0) {
8270 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8272 registerForPreDraw();
8273 if (mEditor != null) mEditor.makeBlink();
8277 if (what == Selection.SELECTION_START) {
8279 newSelStart = newStart;
8281 if (oldStart >= 0 || newStart >= 0) {
8282 int end = Selection.getSelectionEnd(buf);
8283 invalidateCursor(end, oldStart, newStart);
8288 mHighlightPathBogus = true;
8289 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8291 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8292 if (newSelStart < 0) {
8293 newSelStart = Selection.getSelectionStart(buf);
8295 if (newSelEnd < 0) {
8296 newSelEnd = Selection.getSelectionEnd(buf);
8299 if (mEditor != null) {
8300 mEditor.refreshTextActionMode();
8301 if (!hasSelection() && mEditor.mTextActionMode == null && hasTransientState()) {
8302 // User generated selection has been removed.
8303 setHasTransientState(false);
8306 onSelectionChanged(newSelStart, newSelEnd);
8310 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8311 what instanceof CharacterStyle) {
8312 if (ims == null || ims.mBatchEditNesting == 0) {
8314 mHighlightPathBogus = true;
8317 ims.mContentChanged = true;
8319 if (mEditor != null) {
8320 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8321 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8322 mEditor.invalidateHandlesAndActionMode();
8326 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8327 mHighlightPathBogus = true;
8328 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8329 ims.mSelectionModeChanged = true;
8332 if (Selection.getSelectionStart(buf) >= 0) {
8333 if (ims == null || ims.mBatchEditNesting == 0) {
8336 ims.mCursorChanged = true;
8341 if (what instanceof ParcelableSpan) {
8342 // If this is a span that can be sent to a remote process,
8343 // the current extract editor would be interested in it.
8344 if (ims != null && ims.mExtractedTextRequest != null) {
8345 if (ims.mBatchEditNesting != 0) {
8346 if (oldStart >= 0) {
8347 if (ims.mChangedStart > oldStart) {
8348 ims.mChangedStart = oldStart;
8350 if (ims.mChangedStart > oldEnd) {
8351 ims.mChangedStart = oldEnd;
8354 if (newStart >= 0) {
8355 if (ims.mChangedStart > newStart) {
8356 ims.mChangedStart = newStart;
8358 if (ims.mChangedStart > newEnd) {
8359 ims.mChangedStart = newEnd;
8363 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8364 + oldStart + "-" + oldEnd + ","
8365 + newStart + "-" + newEnd + " " + what);
8366 ims.mContentChanged = true;
8371 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8372 what instanceof SpellCheckSpan) {
8373 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8378 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8379 if (isTemporarilyDetached()) {
8380 // If we are temporarily in the detach state, then do nothing.
8381 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8385 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8388 if (mText instanceof Spannable) {
8389 Spannable sp = (Spannable) mText;
8390 MetaKeyKeyListener.resetMetaState(sp);
8394 startStopMarquee(focused);
8396 if (mTransformation != null) {
8397 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8400 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8404 public void onWindowFocusChanged(boolean hasWindowFocus) {
8405 super.onWindowFocusChanged(hasWindowFocus);
8407 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8409 startStopMarquee(hasWindowFocus);
8413 protected void onVisibilityChanged(View changedView, int visibility) {
8414 super.onVisibilityChanged(changedView, visibility);
8415 if (mEditor != null && visibility != VISIBLE) {
8416 mEditor.hideCursorAndSpanControllers();
8417 stopTextActionMode();
8422 * Use {@link BaseInputConnection#removeComposingSpans
8423 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8424 * state from this text view.
8426 public void clearComposingText() {
8427 if (mText instanceof Spannable) {
8428 BaseInputConnection.removeComposingSpans((Spannable)mText);
8433 public void setSelected(boolean selected) {
8434 boolean wasSelected = isSelected();
8436 super.setSelected(selected);
8438 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8448 public boolean onTouchEvent(MotionEvent event) {
8449 final int action = event.getActionMasked();
8450 if (mEditor != null) {
8451 mEditor.onTouchEvent(event);
8453 if (mEditor.mSelectionModifierCursorController != null &&
8454 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8459 final boolean superResult = super.onTouchEvent(event);
8462 * Don't handle the release after a long press, because it will move the selection away from
8463 * whatever the menu action was trying to affect. If the long press should have triggered an
8464 * insertion action mode, we can now actually show it.
8466 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8467 mEditor.mDiscardNextActionUp = false;
8469 if (mEditor.mIsInsertionActionModeStartPending) {
8470 mEditor.startInsertionActionMode();
8471 mEditor.mIsInsertionActionModeStartPending = false;
8476 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8477 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8479 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8480 && mText instanceof Spannable && mLayout != null) {
8481 boolean handled = false;
8483 if (mMovement != null) {
8484 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8487 final boolean textIsSelectable = isTextSelectable();
8488 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8489 // The LinkMovementMethod which should handle taps on links has not been installed
8490 // on non editable text that support text selection.
8491 // We reproduce its behavior here to open links for these.
8492 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8493 getSelectionEnd(), ClickableSpan.class);
8495 if (links.length > 0) {
8496 links[0].onClick(this);
8501 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8502 // Show the IME, except when selecting in read-only text.
8503 final InputMethodManager imm = InputMethodManager.peekInstance();
8505 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8506 handled |= imm != null && imm.showSoftInput(this, 0);
8509 // The above condition ensures that the mEditor is not null
8510 mEditor.onTouchUpEvent(event);
8524 public boolean onGenericMotionEvent(MotionEvent event) {
8525 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8527 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8530 } catch (AbstractMethodError ex) {
8531 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8532 // Ignore its absence in case third party applications implemented the
8533 // interface directly.
8536 return super.onGenericMotionEvent(event);
8540 protected void onCreateContextMenu(ContextMenu menu) {
8541 if (mEditor != null) {
8542 mEditor.onCreateContextMenu(menu);
8547 public boolean showContextMenu() {
8548 if (mEditor != null) {
8549 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
8551 return super.showContextMenu();
8555 public boolean showContextMenu(float x, float y) {
8556 if (mEditor != null) {
8557 mEditor.setContextMenuAnchor(x, y);
8559 return super.showContextMenu(x, y);
8563 * @return True iff this TextView contains a text that can be edited, or if this is
8564 * a selectable TextView.
8566 boolean isTextEditable() {
8567 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8571 * Returns true, only while processing a touch gesture, if the initial
8572 * touch down event caused focus to move to the text view and as a result
8573 * its selection changed. Only valid while processing the touch gesture
8574 * of interest, in an editable text view.
8576 public boolean didTouchFocusSelect() {
8577 return mEditor != null && mEditor.mTouchFocusSelected;
8581 public void cancelLongPress() {
8582 super.cancelLongPress();
8583 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8587 public boolean onTrackballEvent(MotionEvent event) {
8588 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8589 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8594 return super.onTrackballEvent(event);
8597 public void setScroller(Scroller s) {
8602 protected float getLeftFadingEdgeStrength() {
8603 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
8604 final Marquee marquee = mMarquee;
8605 if (marquee.shouldDrawLeftFade()) {
8606 return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f);
8610 } else if (getLineCount() == 1) {
8611 final float lineLeft = getLayout().getLineLeft(0);
8612 if(lineLeft > mScrollX) return 0.0f;
8613 return getHorizontalFadingEdgeStrength(mScrollX, lineLeft);
8615 return super.getLeftFadingEdgeStrength();
8619 protected float getRightFadingEdgeStrength() {
8620 if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) {
8621 final Marquee marquee = mMarquee;
8622 return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll());
8623 } else if (getLineCount() == 1) {
8624 final float rightEdge = mScrollX + (getWidth() - getCompoundPaddingLeft() -
8625 getCompoundPaddingRight());
8626 final float lineRight = getLayout().getLineRight(0);
8627 if(lineRight < rightEdge) return 0.0f;
8628 return getHorizontalFadingEdgeStrength(rightEdge, lineRight);
8630 return super.getRightFadingEdgeStrength();
8634 * Calculates the fading edge strength as the ratio of the distance between two
8635 * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute
8636 * value for the distance calculation.
8638 * @param position1 A horizontal position.
8639 * @param position2 A horizontal position.
8640 * @return Fading edge strength between [0.0f, 1.0f].
8642 @FloatRange(from=0.0, to=1.0)
8643 private final float getHorizontalFadingEdgeStrength(float position1, float position2) {
8644 final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
8645 if(horizontalFadingEdgeLength == 0) return 0.0f;
8646 final float diff = Math.abs(position1 - position2);
8647 if(diff > horizontalFadingEdgeLength) return 1.0f;
8648 return diff / horizontalFadingEdgeLength;
8651 private final boolean isMarqueeFadeEnabled() {
8652 return mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8653 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8657 protected int computeHorizontalScrollRange() {
8658 if (mLayout != null) {
8659 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8660 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8663 return super.computeHorizontalScrollRange();
8667 protected int computeVerticalScrollRange() {
8668 if (mLayout != null)
8669 return mLayout.getHeight();
8671 return super.computeVerticalScrollRange();
8675 protected int computeVerticalScrollExtent() {
8676 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8680 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8681 super.findViewsWithText(outViews, searched, flags);
8682 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8683 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8684 String searchedLowerCase = searched.toString().toLowerCase();
8685 String textLowerCase = mText.toString().toLowerCase();
8686 if (textLowerCase.contains(searchedLowerCase)) {
8692 public enum BufferType {
8693 NORMAL, SPANNABLE, EDITABLE,
8697 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8698 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8699 * if TextView_textColor was not set directly.
8703 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8704 if (attrs == null) {
8705 // Preserve behavior prior to removal of this API.
8706 throw new NullPointerException();
8709 // It's not safe to use this method from apps. The parameter 'attrs'
8710 // must have been obtained using the TextView filter array which is not
8711 // available to the SDK. As such, we grab a default TypedArray with the
8712 // right filter instead here.
8713 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8714 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8715 if (colors == null) {
8716 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8718 final TypedArray appearance = context.obtainStyledAttributes(
8719 ap, R.styleable.TextAppearance);
8720 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8721 appearance.recycle();
8730 * Returns the default color from the TextView_textColor attribute from the
8731 * AttributeSet, if set, or the default color from the
8732 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8733 * TextView_textColor was not set directly.
8737 public static int getTextColor(Context context, TypedArray attrs, int def) {
8738 final ColorStateList colors = getTextColors(context, attrs);
8739 if (colors == null) {
8742 return colors.getDefaultColor();
8747 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8748 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8749 // Handle Ctrl-only shortcuts.
8751 case KeyEvent.KEYCODE_A:
8752 if (canSelectText()) {
8753 return onTextContextMenuItem(ID_SELECT_ALL);
8756 case KeyEvent.KEYCODE_Z:
8758 return onTextContextMenuItem(ID_UNDO);
8761 case KeyEvent.KEYCODE_X:
8763 return onTextContextMenuItem(ID_CUT);
8766 case KeyEvent.KEYCODE_C:
8768 return onTextContextMenuItem(ID_COPY);
8771 case KeyEvent.KEYCODE_V:
8773 return onTextContextMenuItem(ID_PASTE);
8777 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8778 // Handle Ctrl-Shift shortcuts.
8780 case KeyEvent.KEYCODE_Z:
8782 return onTextContextMenuItem(ID_REDO);
8785 case KeyEvent.KEYCODE_V:
8787 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8791 return super.onKeyShortcut(keyCode, event);
8795 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8796 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8797 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8800 boolean canSelectText() {
8801 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8805 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8806 * The text must be spannable and the movement method must allow for arbitary selection.
8808 * See also {@link #canSelectText()}.
8810 boolean textCanBeSelected() {
8811 // prepareCursorController() relies on this method.
8812 // If you change this condition, make sure prepareCursorController is called anywhere
8813 // the value of this condition might be changed.
8814 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8815 return isTextEditable() ||
8816 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8819 private Locale getTextServicesLocale(boolean allowNullLocale) {
8820 // Start fetching the text services locale asynchronously.
8821 updateTextServicesLocaleAsync();
8822 // If !allowNullLocale and there is no cached text services locale, just return the default
8824 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8825 : mCurrentSpellCheckerLocaleCache;
8829 * This is a temporary method. Future versions may support multi-locale text.
8830 * Caveat: This method may not return the latest text services locale, but this should be
8831 * acceptable and it's more important to make this method asynchronous.
8833 * @return The locale that should be used for a word iterator
8834 * in this TextView, based on the current spell checker settings,
8835 * the current IME's locale, or the system default locale.
8836 * Please note that a word iterator in this TextView is different from another word iterator
8837 * used by SpellChecker.java of TextView. This method should be used for the former.
8840 // TODO: Support multi-locale
8841 // TODO: Update the text services locale immediately after the keyboard locale is switched
8842 // by catching intent of keyboard switch event
8843 public Locale getTextServicesLocale() {
8844 return getTextServicesLocale(false /* allowNullLocale */);
8848 * @return true if this TextView is specialized for showing and interacting with the extracted
8849 * text in a full-screen input method.
8852 public boolean isInExtractedMode() {
8857 * This is a temporary method. Future versions may support multi-locale text.
8858 * Caveat: This method may not return the latest spell checker locale, but this should be
8859 * acceptable and it's more important to make this method asynchronous.
8861 * @return The locale that should be used for a spell checker in this TextView,
8862 * based on the current spell checker settings, the current IME's locale, or the system default
8866 public Locale getSpellCheckerLocale() {
8867 return getTextServicesLocale(true /* allowNullLocale */);
8870 private void updateTextServicesLocaleAsync() {
8871 // AsyncTask.execute() uses a serial executor which means we don't have
8872 // to lock around updateTextServicesLocaleLocked() to prevent it from
8873 // being executed n times in parallel.
8874 AsyncTask.execute(new Runnable() {
8877 updateTextServicesLocaleLocked();
8882 private void updateTextServicesLocaleLocked() {
8883 final TextServicesManager textServicesManager = (TextServicesManager)
8884 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8885 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8886 final Locale locale;
8887 if (subtype != null) {
8888 locale = subtype.getLocaleObject();
8892 mCurrentSpellCheckerLocaleCache = locale;
8895 void onLocaleChanged() {
8896 mEditor.onLocaleChanged();
8900 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8901 * Made available to achieve a consistent behavior.
8904 public WordIterator getWordIterator() {
8905 if (mEditor != null) {
8906 return mEditor.getWordIterator();
8914 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8915 super.onPopulateAccessibilityEventInternal(event);
8917 final CharSequence text = getTextForAccessibility();
8918 if (!TextUtils.isEmpty(text)) {
8919 event.getText().add(text);
8924 * @return true if the user has explicitly allowed accessibility services
8925 * to speak passwords.
8927 private boolean shouldSpeakPasswordsForAccessibility() {
8928 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8929 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8930 UserHandle.USER_CURRENT_OR_SELF) == 1);
8934 public CharSequence getAccessibilityClassName() {
8935 return TextView.class.getName();
8939 public void onProvideStructure(ViewStructure structure) {
8940 super.onProvideStructure(structure);
8941 final boolean isPassword = hasPasswordTransformationMethod()
8942 || isPasswordInputType(getInputType());
8944 if (mLayout == null) {
8947 Layout layout = mLayout;
8948 final int lineCount = layout.getLineCount();
8949 if (lineCount <= 1) {
8950 // Simple case: this is a single line.
8951 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8953 // Complex case: multi-line, could be scrolled or within a scroll container
8954 // so some lines are not visible.
8955 final int[] tmpCords = new int[2];
8956 getLocationInWindow(tmpCords);
8957 final int topWindowLocation = tmpCords[1];
8959 ViewParent viewParent = getParent();
8960 while (viewParent instanceof View) {
8961 root = (View) viewParent;
8962 viewParent = root.getParent();
8964 final int windowHeight = root.getHeight();
8966 final int bottomLine;
8967 if (topWindowLocation >= 0) {
8968 // The top of the view is fully within its window; start text at line 0.
8969 topLine = getLineAtCoordinateUnclamped(0);
8970 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
8972 // The top of hte window has scrolled off the top of the window; figure out
8973 // the starting line for this.
8974 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
8975 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
8977 // We want to return some contextual lines above/below the lines that are
8978 // actually visible.
8979 int expandedTopLine = topLine - (bottomLine-topLine)/2;
8980 if (expandedTopLine < 0) {
8981 expandedTopLine = 0;
8983 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
8984 if (expandedBottomLine >= lineCount) {
8985 expandedBottomLine = lineCount-1;
8987 // Convert lines into character offsets.
8988 int expandedTopChar = layout.getLineStart(expandedTopLine);
8989 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
8990 // Take into account selection -- if there is a selection, we need to expand
8991 // the text we are returning to include that selection.
8992 final int selStart = getSelectionStart();
8993 final int selEnd = getSelectionEnd();
8994 if (selStart < selEnd) {
8995 if (selStart < expandedTopChar) {
8996 expandedTopChar = selStart;
8998 if (selEnd > expandedBottomChar) {
8999 expandedBottomChar = selEnd;
9002 // Get the text and trim it to the range we are reporting.
9003 CharSequence text = getText();
9004 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9005 text = text.subSequence(expandedTopChar, expandedBottomChar);
9007 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
9008 final int[] lineOffsets = new int[bottomLine-topLine+1];
9009 final int[] lineBaselines = new int[bottomLine-topLine+1];
9010 final int baselineOffset = getBaselineOffset();
9011 for (int i=topLine; i<=bottomLine; i++) {
9012 lineOffsets[i-topLine] = layout.getLineStart(i);
9013 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
9015 structure.setTextLines(lineOffsets, lineBaselines);
9018 // Extract style information that applies to the TextView as a whole.
9020 int typefaceStyle = getTypefaceStyle();
9021 if ((typefaceStyle & Typeface.BOLD) != 0) {
9022 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9024 if ((typefaceStyle & Typeface.ITALIC) != 0) {
9025 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9028 // Global styles can also be set via TextView.setPaintFlags().
9029 int paintFlags = mTextPaint.getFlags();
9030 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9031 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9033 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9034 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9036 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9037 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9040 // TextView does not have its own text background color. A background is either part
9041 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9042 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9043 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
9045 structure.setHint(getHint());
9050 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9051 super.onInitializeAccessibilityEventInternal(event);
9053 final boolean isPassword = hasPasswordTransformationMethod();
9054 event.setPassword(isPassword);
9056 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9057 event.setFromIndex(Selection.getSelectionStart(mText));
9058 event.setToIndex(Selection.getSelectionEnd(mText));
9059 event.setItemCount(mText.length());
9065 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9066 super.onInitializeAccessibilityNodeInfoInternal(info);
9068 final boolean isPassword = hasPasswordTransformationMethod();
9069 info.setPassword(isPassword);
9070 info.setText(getTextForAccessibility());
9072 if (mBufferType == BufferType.EDITABLE) {
9073 info.setEditable(true);
9075 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT);
9079 if (mEditor != null) {
9080 info.setInputType(mEditor.mInputType);
9082 if (mEditor.mError != null) {
9083 info.setContentInvalid(true);
9084 info.setError(mEditor.mError);
9088 if (!TextUtils.isEmpty(mText)) {
9089 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9090 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9091 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9092 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9093 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9094 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9095 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
9096 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
9101 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9104 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9107 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9110 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9111 ACCESSIBILITY_ACTION_SHARE,
9112 getResources().getString(com.android.internal.R.string.share)));
9114 if (canProcessText()) { // also implies mEditor is not null.
9115 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9119 // Check for known input filter types.
9120 final int numFilters = mFilters.length;
9121 for (int i = 0; i < numFilters; i++) {
9122 final InputFilter filter = mFilters[i];
9123 if (filter instanceof InputFilter.LengthFilter) {
9124 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9128 if (!isSingleLine()) {
9129 info.setMultiLine(true);
9134 * Performs an accessibility action after it has been offered to the
9140 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
9142 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9146 case AccessibilityNodeInfo.ACTION_CLICK: {
9147 return performAccessibilityActionClick(arguments);
9149 case AccessibilityNodeInfo.ACTION_COPY: {
9150 if (isFocused() && canCopy()) {
9151 if (onTextContextMenuItem(ID_COPY)) {
9156 case AccessibilityNodeInfo.ACTION_PASTE: {
9157 if (isFocused() && canPaste()) {
9158 if (onTextContextMenuItem(ID_PASTE)) {
9163 case AccessibilityNodeInfo.ACTION_CUT: {
9164 if (isFocused() && canCut()) {
9165 if (onTextContextMenuItem(ID_CUT)) {
9170 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9171 ensureIterableTextForAccessibilitySelectable();
9172 CharSequence text = getIterableTextForAccessibility();
9176 final int start = (arguments != null) ? arguments.getInt(
9177 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9178 final int end = (arguments != null) ? arguments.getInt(
9179 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9180 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9181 // No arguments clears the selection.
9182 if (start == end && end == -1) {
9183 Selection.removeSelection((Spannable) text);
9186 if (start >= 0 && start <= end && end <= text.length()) {
9187 Selection.setSelection((Spannable) text, start, end);
9188 // Make sure selection mode is engaged.
9189 if (mEditor != null) {
9190 mEditor.startSelectionActionMode();
9196 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9197 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9198 ensureIterableTextForAccessibilitySelectable();
9199 return super.performAccessibilityActionInternal(action, arguments);
9201 case ACCESSIBILITY_ACTION_SHARE: {
9202 if (isFocused() && canShare()) {
9203 if (onTextContextMenuItem(ID_SHARE)) {
9208 case AccessibilityNodeInfo.ACTION_SET_TEXT: {
9209 if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) {
9212 CharSequence text = (arguments != null) ? arguments.getCharSequence(
9213 AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
9215 if (mText != null) {
9216 int updatedTextLength = mText.length();
9217 if (updatedTextLength > 0) {
9218 Selection.setSelection((Spannable) mText, updatedTextLength);
9223 return super.performAccessibilityActionInternal(action, arguments);
9228 private boolean performAccessibilityActionClick(Bundle arguments) {
9229 boolean handled = false;
9235 if (isClickable() || isLongClickable()) {
9236 // Simulate View.onTouchEvent for an ACTION_UP event
9237 if (isFocusable() && !isFocused()) {
9245 // Show the IME, except when selecting in read-only text.
9246 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9247 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9248 final InputMethodManager imm = InputMethodManager.peekInstance();
9250 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9251 handled |= imm.showSoftInput(this, 0);
9258 private boolean hasSpannableText() {
9259 return mText != null && mText instanceof Spannable;
9264 public void sendAccessibilityEventInternal(int eventType) {
9265 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9266 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9269 // Do not send scroll events since first they are not interesting for
9270 // accessibility and second such events a generated too frequently.
9271 // For details see the implementation of bringTextIntoView().
9272 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9275 super.sendAccessibilityEventInternal(eventType);
9279 * Returns the text that should be exposed to accessibility services.
9281 * This approximates what is displayed visually. If the user has specified
9282 * that accessibility services should speak passwords, this method will
9283 * bypass any password transformation method and return unobscured text.
9285 * @return the text that should be exposed to accessibility services, may
9286 * be {@code null} if no text is set
9289 private CharSequence getTextForAccessibility() {
9290 // If the text is empty, we must be showing the hint text.
9291 if (TextUtils.isEmpty(mText)) {
9295 // Check whether we need to bypass the transformation
9296 // method and expose unobscured text.
9297 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9301 // Otherwise, speak whatever text is being displayed.
9302 return mTransformed;
9305 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9306 int fromIndex, int removedCount, int addedCount) {
9307 AccessibilityEvent event =
9308 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9309 event.setFromIndex(fromIndex);
9310 event.setRemovedCount(removedCount);
9311 event.setAddedCount(addedCount);
9312 event.setBeforeText(beforeText);
9313 sendAccessibilityEventUnchecked(event);
9317 * Returns whether this text view is a current input method target. The
9318 * default implementation just checks with {@link InputMethodManager}.
9320 public boolean isInputMethodTarget() {
9321 InputMethodManager imm = InputMethodManager.peekInstance();
9322 return imm != null && imm.isActive(this);
9325 static final int ID_SELECT_ALL = android.R.id.selectAll;
9326 static final int ID_UNDO = android.R.id.undo;
9327 static final int ID_REDO = android.R.id.redo;
9328 static final int ID_CUT = android.R.id.cut;
9329 static final int ID_COPY = android.R.id.copy;
9330 static final int ID_PASTE = android.R.id.paste;
9331 static final int ID_SHARE = android.R.id.shareText;
9332 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9333 static final int ID_REPLACE = android.R.id.replaceText;
9336 * Called when a context menu option for the text view is selected. Currently
9337 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9338 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9340 * @return true if the context menu item action was performed.
9342 public boolean onTextContextMenuItem(int id) {
9344 int max = mText.length();
9347 final int selStart = getSelectionStart();
9348 final int selEnd = getSelectionEnd();
9350 min = Math.max(0, Math.min(selStart, selEnd));
9351 max = Math.max(0, Math.max(selStart, selEnd));
9360 if (mEditor != null) {
9363 return true; // Returns true even if nothing was undone.
9366 if (mEditor != null) {
9369 return true; // Returns true even if nothing was undone.
9372 paste(min, max, true /* withFormatting */);
9375 case ID_PASTE_AS_PLAIN_TEXT:
9376 paste(min, max, false /* withFormatting */);
9380 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9381 deleteText_internal(min, max);
9385 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9386 stopTextActionMode();
9390 if (mEditor != null) {
9396 shareSelectedText();
9402 CharSequence getTransformedText(int start, int end) {
9403 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9407 public boolean performLongClick() {
9408 boolean handled = false;
9410 if (mEditor != null) {
9411 mEditor.mIsBeingLongClicked = true;
9414 if (super.performLongClick()) {
9418 if (mEditor != null) {
9419 handled |= mEditor.performLongClick(handled);
9420 mEditor.mIsBeingLongClicked = false;
9424 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9425 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9432 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9433 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9434 if (mEditor != null) {
9435 mEditor.onScrollChanged();
9440 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9441 * by the IME or by the spell checker as the user types. This is done by adding
9442 * {@link SuggestionSpan}s to the text.
9444 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9445 * user asks for them on these parts of the text. This value depends on the inputType of this
9448 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9450 * In addition, the type variation must be one of
9451 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9452 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9453 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9454 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9455 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9457 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9459 * @return true if the suggestions popup window is enabled, based on the inputType.
9461 public boolean isSuggestionsEnabled() {
9462 if (mEditor == null) return false;
9463 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9466 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9468 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9469 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9470 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9471 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9472 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9473 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9477 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9478 * selection is initiated in this View.
9480 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9481 * Paste, Replace and Share actions, depending on what this View supports.
9483 * <p>A custom implementation can add new entries in the default menu in its
9484 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9485 * method. The default actions can also be removed from the menu using
9486 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9487 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9488 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9490 * <p>Returning false from
9491 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9492 * will prevent the action mode from being started.
9494 * <p>Action click events should be handled by the custom implementation of
9495 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9496 * android.view.MenuItem)}.
9498 * <p>Note that text selection mode is not started when a TextView receives focus and the
9499 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9500 * that case, to allow for quick replacement.
9502 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9503 createEditorIfNeeded();
9504 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9508 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9510 * @return The current custom selection callback.
9512 public ActionMode.Callback getCustomSelectionActionModeCallback() {
9513 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9517 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9518 * insertion is initiated in this View.
9519 * The standard implementation populates the menu with a subset of Select All,
9520 * Paste and Replace actions, depending on what this View supports.
9522 * <p>A custom implementation can add new entries in the default menu in its
9523 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9524 * android.view.Menu)} method. The default actions can also be removed from the menu using
9525 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9526 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9528 * <p>Returning false from
9529 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9530 * android.view.Menu)} will prevent the action mode from being started.</p>
9532 * <p>Action click events should be handled by the custom implementation of
9533 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9534 * android.view.MenuItem)}.</p>
9536 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9537 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9539 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9540 createEditorIfNeeded();
9541 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9545 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9547 * @return The current custom insertion callback.
9549 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9550 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9556 protected void stopTextActionMode() {
9557 if (mEditor != null) {
9558 mEditor.stopTextActionMode();
9563 return mEditor != null && mEditor.canUndo();
9567 return mEditor != null && mEditor.canRedo();
9571 if (hasPasswordTransformationMethod()) {
9575 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9576 mEditor.mKeyListener != null) {
9584 if (hasPasswordTransformationMethod()) {
9588 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9595 boolean canShare() {
9596 if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
9602 boolean isDeviceProvisioned() {
9603 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
9604 mDeviceProvisionedState = Settings.Global.getInt(
9605 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
9606 ? DEVICE_PROVISIONED_YES
9607 : DEVICE_PROVISIONED_NO;
9609 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
9612 boolean canPaste() {
9613 return (mText instanceof Editable &&
9614 mEditor != null && mEditor.mKeyListener != null &&
9615 getSelectionStart() >= 0 &&
9616 getSelectionEnd() >= 0 &&
9617 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9621 boolean canProcessText() {
9622 if (getId() == View.NO_ID) {
9628 boolean canSelectAllText() {
9629 return canSelectText() && !hasPasswordTransformationMethod()
9630 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9633 boolean selectAllText() {
9634 final int length = mText.length();
9635 Selection.setSelection((Spannable) mText, 0, length);
9639 void replaceSelectionWithText(CharSequence text) {
9640 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9644 * Paste clipboard content between min and max positions.
9646 private void paste(int min, int max, boolean withFormatting) {
9647 ClipboardManager clipboard =
9648 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9649 ClipData clip = clipboard.getPrimaryClip();
9651 boolean didFirst = false;
9652 for (int i=0; i<clip.getItemCount(); i++) {
9653 final CharSequence paste;
9654 if (withFormatting) {
9655 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9657 // Get an item as text and remove all spans by toString().
9658 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9659 paste = (text instanceof Spanned) ? text.toString() : text;
9661 if (paste != null) {
9663 Selection.setSelection((Spannable) mText, max);
9664 ((Editable) mText).replace(min, max, paste);
9667 ((Editable) mText).insert(getSelectionEnd(), "\n");
9668 ((Editable) mText).insert(getSelectionEnd(), paste);
9672 sLastCutCopyOrTextChangedTime = 0;
9676 private void shareSelectedText() {
9677 String selectedText = getSelectedText();
9678 if (selectedText != null && !selectedText.isEmpty()) {
9679 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9680 sharingIntent.setType("text/plain");
9681 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9682 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9683 getContext().startActivity(Intent.createChooser(sharingIntent, null));
9684 Selection.setSelection((Spannable) mText, getSelectionEnd());
9688 private void setPrimaryClip(ClipData clip) {
9689 ClipboardManager clipboard = (ClipboardManager) getContext().
9690 getSystemService(Context.CLIPBOARD_SERVICE);
9691 clipboard.setPrimaryClip(clip);
9692 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9696 * Get the character offset closest to the specified absolute position. A typical use case is to
9697 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9699 * @param x The horizontal absolute position of a point on screen
9700 * @param y The vertical absolute position of a point on screen
9701 * @return the character offset for the character whose position is closest to the specified
9702 * position. Returns -1 if there is no layout.
9704 public int getOffsetForPosition(float x, float y) {
9705 if (getLayout() == null) return -1;
9706 final int line = getLineAtCoordinate(y);
9707 final int offset = getOffsetAtCoordinate(line, x);
9711 float convertToLocalHorizontalCoordinate(float x) {
9712 x -= getTotalPaddingLeft();
9713 // Clamp the position to inside of the view.
9714 x = Math.max(0.0f, x);
9715 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9720 int getLineAtCoordinate(float y) {
9721 y -= getTotalPaddingTop();
9722 // Clamp the position to inside of the view.
9723 y = Math.max(0.0f, y);
9724 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9726 return getLayout().getLineForVertical((int) y);
9729 int getLineAtCoordinateUnclamped(float y) {
9730 y -= getTotalPaddingTop();
9732 return getLayout().getLineForVertical((int) y);
9735 int getOffsetAtCoordinate(int line, float x) {
9736 x = convertToLocalHorizontalCoordinate(x);
9737 return getLayout().getOffsetForHorizontal(line, x);
9741 public boolean onDragEvent(DragEvent event) {
9742 switch (event.getAction()) {
9743 case DragEvent.ACTION_DRAG_STARTED:
9744 return mEditor != null && mEditor.hasInsertionController();
9746 case DragEvent.ACTION_DRAG_ENTERED:
9747 TextView.this.requestFocus();
9750 case DragEvent.ACTION_DRAG_LOCATION:
9751 final int offset = getOffsetForPosition(event.getX(), event.getY());
9752 Selection.setSelection((Spannable)mText, offset);
9755 case DragEvent.ACTION_DROP:
9756 if (mEditor != null) mEditor.onDrop(event);
9759 case DragEvent.ACTION_DRAG_ENDED:
9760 case DragEvent.ACTION_DRAG_EXITED:
9766 boolean isInBatchEditMode() {
9767 if (mEditor == null) return false;
9768 final Editor.InputMethodState ims = mEditor.mInputMethodState;
9770 return ims.mBatchEditNesting > 0;
9772 return mEditor.mInBatchEditControllers;
9776 public void onRtlPropertiesChanged(int layoutDirection) {
9777 super.onRtlPropertiesChanged(layoutDirection);
9779 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9780 if (mTextDir != newTextDir) {
9781 mTextDir = newTextDir;
9782 if (mLayout != null) {
9791 protected TextDirectionHeuristic getTextDirectionHeuristic() {
9792 if (hasPasswordTransformationMethod()) {
9793 // passwords fields should be LTR
9794 return TextDirectionHeuristics.LTR;
9797 // Always need to resolve layout direction first
9798 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9800 // Now, we can select the heuristic
9801 switch (getTextDirection()) {
9803 case TEXT_DIRECTION_FIRST_STRONG:
9804 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9805 TextDirectionHeuristics.FIRSTSTRONG_LTR);
9806 case TEXT_DIRECTION_ANY_RTL:
9807 return TextDirectionHeuristics.ANYRTL_LTR;
9808 case TEXT_DIRECTION_LTR:
9809 return TextDirectionHeuristics.LTR;
9810 case TEXT_DIRECTION_RTL:
9811 return TextDirectionHeuristics.RTL;
9812 case TEXT_DIRECTION_LOCALE:
9813 return TextDirectionHeuristics.LOCALE;
9814 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9815 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9816 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9817 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9825 public void onResolveDrawables(int layoutDirection) {
9826 // No need to resolve twice
9827 if (mLastLayoutDirection == layoutDirection) {
9830 mLastLayoutDirection = layoutDirection;
9832 // Resolve drawables
9833 if (mDrawables != null) {
9834 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
9835 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
9836 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
9837 applyCompoundDrawableTint();
9843 * Prepares a drawable for display by propagating layout direction and
9846 * @param dr the drawable to prepare
9848 private void prepareDrawableForDisplay(@Nullable Drawable dr) {
9853 dr.setLayoutDirection(getLayoutDirection());
9855 if (dr.isStateful()) {
9856 dr.setState(getDrawableState());
9857 dr.jumpToCurrentState();
9864 protected void resetResolvedDrawables() {
9865 super.resetResolvedDrawables();
9866 mLastLayoutDirection = -1;
9872 protected void viewClicked(InputMethodManager imm) {
9874 imm.viewClicked(this);
9879 * Deletes the range of text [start, end[.
9882 protected void deleteText_internal(int start, int end) {
9883 ((Editable) mText).delete(start, end);
9887 * Replaces the range of text [start, end[ by replacement text
9890 protected void replaceText_internal(int start, int end, CharSequence text) {
9891 ((Editable) mText).replace(start, end, text);
9895 * Sets a span on the specified range of text
9898 protected void setSpan_internal(Object span, int start, int end, int flags) {
9899 ((Editable) mText).setSpan(span, start, end, flags);
9903 * Moves the cursor to the specified offset position in text
9906 protected void setCursorPosition_internal(int start, int end) {
9907 Selection.setSelection(((Editable) mText), start, end);
9911 * An Editor should be created as soon as any of the editable-specific fields (grouped
9912 * inside the Editor object) is assigned to a non-default value.
9913 * This method will create the Editor if needed.
9915 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9916 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9917 * Editor for backward compatibility, as soon as one of these fields is assigned.
9919 * Also note that for performance reasons, the mEditor is created when needed, but not
9920 * reset when no more edit-specific fields are needed.
9922 private void createEditorIfNeeded() {
9923 if (mEditor == null) {
9924 mEditor = new Editor(this);
9932 public CharSequence getIterableTextForAccessibility() {
9936 private void ensureIterableTextForAccessibilitySelectable() {
9937 if (!(mText instanceof Spannable)) {
9938 setText(mText, BufferType.SPANNABLE);
9946 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9947 switch (granularity) {
9948 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9949 Spannable text = (Spannable) getIterableTextForAccessibility();
9950 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9951 AccessibilityIterators.LineTextSegmentIterator iterator =
9952 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9953 iterator.initialize(text, getLayout());
9957 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9958 Spannable text = (Spannable) getIterableTextForAccessibility();
9959 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9960 AccessibilityIterators.PageTextSegmentIterator iterator =
9961 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9962 iterator.initialize(this);
9967 return super.getIteratorForGranularity(granularity);
9974 public int getAccessibilitySelectionStart() {
9975 return getSelectionStart();
9981 public boolean isAccessibilitySelectionExtendable() {
9989 public int getAccessibilitySelectionEnd() {
9990 return getSelectionEnd();
9997 public void setAccessibilitySelection(int start, int end) {
9998 if (getAccessibilitySelectionStart() == start
9999 && getAccessibilitySelectionEnd() == end) {
10002 CharSequence text = getIterableTextForAccessibility();
10003 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
10004 Selection.setSelection((Spannable) text, start, end);
10006 Selection.removeSelection((Spannable) text);
10008 // Hide all selection controllers used for adjusting selection
10009 // since we are doing so explicitlty by other means and these
10010 // controllers interact with how selection behaves.
10011 if (mEditor != null) {
10012 mEditor.hideCursorAndSpanControllers();
10013 mEditor.stopTextActionMode();
10019 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10020 super.encodeProperties(stream);
10022 TruncateAt ellipsize = getEllipsize();
10023 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10024 stream.addProperty("text:textSize", getTextSize());
10025 stream.addProperty("text:scaledTextSize", getScaledTextSize());
10026 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10027 stream.addProperty("text:selectionStart", getSelectionStart());
10028 stream.addProperty("text:selectionEnd", getSelectionEnd());
10029 stream.addProperty("text:curTextColor", mCurTextColor);
10030 stream.addProperty("text:text", mText == null ? null : mText.toString());
10031 stream.addProperty("text:gravity", mGravity);
10035 * User interface state that is stored by TextView for implementing
10036 * {@link View#onSaveInstanceState}.
10038 public static class SavedState extends BaseSavedState {
10042 boolean frozenWithFocus;
10043 CharSequence error;
10044 ParcelableParcel editorState; // Optional state from Editor.
10046 SavedState(Parcelable superState) {
10051 public void writeToParcel(Parcel out, int flags) {
10052 super.writeToParcel(out, flags);
10053 out.writeInt(selStart);
10054 out.writeInt(selEnd);
10055 out.writeInt(frozenWithFocus ? 1 : 0);
10056 TextUtils.writeToParcel(text, out, flags);
10058 if (error == null) {
10062 TextUtils.writeToParcel(error, out, flags);
10065 if (editorState == null) {
10069 editorState.writeToParcel(out, flags);
10074 public String toString() {
10075 String str = "TextView.SavedState{"
10076 + Integer.toHexString(System.identityHashCode(this))
10077 + " start=" + selStart + " end=" + selEnd;
10078 if (text != null) {
10079 str += " text=" + text;
10084 @SuppressWarnings("hiding")
10085 public static final Parcelable.Creator<SavedState> CREATOR
10086 = new Parcelable.Creator<SavedState>() {
10087 public SavedState createFromParcel(Parcel in) {
10088 return new SavedState(in);
10091 public SavedState[] newArray(int size) {
10092 return new SavedState[size];
10096 private SavedState(Parcel in) {
10098 selStart = in.readInt();
10099 selEnd = in.readInt();
10100 frozenWithFocus = (in.readInt() != 0);
10101 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10103 if (in.readInt() != 0) {
10104 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10107 if (in.readInt() != 0) {
10108 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10113 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10114 private char[] mChars;
10115 private int mStart, mLength;
10117 public CharWrapper(char[] chars, int start, int len) {
10123 /* package */ void set(char[] chars, int start, int len) {
10129 public int length() {
10133 public char charAt(int off) {
10134 return mChars[off + mStart];
10138 public String toString() {
10139 return new String(mChars, mStart, mLength);
10142 public CharSequence subSequence(int start, int end) {
10143 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10144 throw new IndexOutOfBoundsException(start + ", " + end);
10147 return new String(mChars, start + mStart, end - start);
10150 public void getChars(int start, int end, char[] buf, int off) {
10151 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10152 throw new IndexOutOfBoundsException(start + ", " + end);
10155 System.arraycopy(mChars, start + mStart, buf, off, end - start);
10158 public void drawText(Canvas c, int start, int end,
10159 float x, float y, Paint p) {
10160 c.drawText(mChars, start + mStart, end - start, x, y, p);
10163 public void drawTextRun(Canvas c, int start, int end,
10164 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
10165 int count = end - start;
10166 int contextCount = contextEnd - contextStart;
10167 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
10168 contextCount, x, y, isRtl, p);
10171 public float measureText(int start, int end, Paint p) {
10172 return p.measureText(mChars, start + mStart, end - start);
10175 public int getTextWidths(int start, int end, float[] widths, Paint p) {
10176 return p.getTextWidths(mChars, start + mStart, end - start, widths);
10179 public float getTextRunAdvances(int start, int end, int contextStart,
10180 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
10182 int count = end - start;
10183 int contextCount = contextEnd - contextStart;
10184 return p.getTextRunAdvances(mChars, start + mStart, count,
10185 contextStart + mStart, contextCount, isRtl, advances,
10189 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
10190 int offset, int cursorOpt, Paint p) {
10191 int contextCount = contextEnd - contextStart;
10192 return p.getTextRunCursor(mChars, contextStart + mStart,
10193 contextCount, dir, offset + mStart, cursorOpt);
10197 private static final class Marquee {
10198 // TODO: Add an option to configure this
10199 private static final float MARQUEE_DELTA_MAX = 0.07f;
10200 private static final int MARQUEE_DELAY = 1200;
10201 private static final int MARQUEE_DP_PER_SECOND = 30;
10203 private static final byte MARQUEE_STOPPED = 0x0;
10204 private static final byte MARQUEE_STARTING = 0x1;
10205 private static final byte MARQUEE_RUNNING = 0x2;
10207 private final WeakReference<TextView> mView;
10208 private final Choreographer mChoreographer;
10210 private byte mStatus = MARQUEE_STOPPED;
10211 private final float mPixelsPerSecond;
10212 private float mMaxScroll;
10213 private float mMaxFadeScroll;
10214 private float mGhostStart;
10215 private float mGhostOffset;
10216 private float mFadeStop;
10217 private int mRepeatLimit;
10219 private float mScroll;
10220 private long mLastAnimationMs;
10222 Marquee(TextView v) {
10223 final float density = v.getContext().getResources().getDisplayMetrics().density;
10224 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10225 mView = new WeakReference<TextView>(v);
10226 mChoreographer = Choreographer.getInstance();
10229 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10231 public void doFrame(long frameTimeNanos) {
10236 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10238 public void doFrame(long frameTimeNanos) {
10239 mStatus = MARQUEE_RUNNING;
10240 mLastAnimationMs = mChoreographer.getFrameTime();
10245 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10247 public void doFrame(long frameTimeNanos) {
10248 if (mStatus == MARQUEE_RUNNING) {
10249 if (mRepeatLimit >= 0) {
10252 start(mRepeatLimit);
10258 if (mStatus != MARQUEE_RUNNING) {
10262 mChoreographer.removeFrameCallback(mTickCallback);
10264 final TextView textView = mView.get();
10265 if (textView != null && (textView.isFocused() || textView.isSelected())) {
10266 long currentMs = mChoreographer.getFrameTime();
10267 long deltaMs = currentMs - mLastAnimationMs;
10268 mLastAnimationMs = currentMs;
10269 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10270 mScroll += deltaPx;
10271 if (mScroll > mMaxScroll) {
10272 mScroll = mMaxScroll;
10273 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10275 mChoreographer.postFrameCallback(mTickCallback);
10277 textView.invalidate();
10282 mStatus = MARQUEE_STOPPED;
10283 mChoreographer.removeFrameCallback(mStartCallback);
10284 mChoreographer.removeFrameCallback(mRestartCallback);
10285 mChoreographer.removeFrameCallback(mTickCallback);
10289 private void resetScroll() {
10291 final TextView textView = mView.get();
10292 if (textView != null) textView.invalidate();
10295 void start(int repeatLimit) {
10296 if (repeatLimit == 0) {
10300 mRepeatLimit = repeatLimit;
10301 final TextView textView = mView.get();
10302 if (textView != null && textView.mLayout != null) {
10303 mStatus = MARQUEE_STARTING;
10305 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10306 textView.getCompoundPaddingRight();
10307 final float lineWidth = textView.mLayout.getLineWidth(0);
10308 final float gap = textWidth / 3.0f;
10309 mGhostStart = lineWidth - textWidth + gap;
10310 mMaxScroll = mGhostStart + textWidth;
10311 mGhostOffset = lineWidth + gap;
10312 mFadeStop = lineWidth + textWidth / 6.0f;
10313 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10315 textView.invalidate();
10316 mChoreographer.postFrameCallback(mStartCallback);
10320 float getGhostOffset() {
10321 return mGhostOffset;
10324 float getScroll() {
10328 float getMaxFadeScroll() {
10329 return mMaxFadeScroll;
10332 boolean shouldDrawLeftFade() {
10333 return mScroll <= mFadeStop;
10336 boolean shouldDrawGhost() {
10337 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10340 boolean isRunning() {
10341 return mStatus == MARQUEE_RUNNING;
10344 boolean isStopped() {
10345 return mStatus == MARQUEE_STOPPED;
10349 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10351 private CharSequence mBeforeText;
10353 public void beforeTextChanged(CharSequence buffer, int start,
10354 int before, int after) {
10355 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10356 + " before=" + before + " after=" + after + ": " + buffer);
10358 if (AccessibilityManager.getInstance(mContext).isEnabled()
10359 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10360 || shouldSpeakPasswordsForAccessibility())) {
10361 mBeforeText = buffer.toString();
10364 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10367 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10368 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10369 + " before=" + before + " after=" + after + ": " + buffer);
10370 TextView.this.handleTextChanged(buffer, start, before, after);
10372 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10373 (isFocused() || isSelected() && isShown())) {
10374 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10375 mBeforeText = null;
10379 public void afterTextChanged(Editable buffer) {
10380 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10381 TextView.this.sendAfterTextChanged(buffer);
10383 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10384 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10388 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10389 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10390 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10391 TextView.this.spanChange(buf, what, s, st, e, en);
10394 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10395 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10396 + " what=" + what + ": " + buf);
10397 TextView.this.spanChange(buf, what, -1, s, -1, e);
10400 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10401 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10402 + " what=" + what + ": " + buf);
10403 TextView.this.spanChange(buf, what, s, -1, e, -1);