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;
21 import android.annotation.ColorInt;
22 import android.annotation.DrawableRes;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.Size;
26 import android.annotation.StringRes;
27 import android.annotation.StyleRes;
28 import android.annotation.XmlRes;
29 import android.app.Activity;
30 import android.app.assist.AssistStructure;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.UndoManager;
36 import android.content.res.ColorStateList;
37 import android.content.res.CompatibilityInfo;
38 import android.content.res.Configuration;
39 import android.content.res.Resources;
40 import android.content.res.TypedArray;
41 import android.content.res.XmlResourceParser;
42 import android.graphics.Canvas;
43 import android.graphics.Insets;
44 import android.graphics.Paint;
45 import android.graphics.Path;
46 import android.graphics.PorterDuff;
47 import android.graphics.Rect;
48 import android.graphics.RectF;
49 import android.graphics.Typeface;
50 import android.graphics.drawable.Drawable;
51 import android.os.AsyncTask;
52 import android.os.Bundle;
53 import android.os.Parcel;
54 import android.os.Parcelable;
55 import android.os.ParcelableParcel;
56 import android.os.SystemClock;
57 import android.os.UserHandle;
58 import android.provider.Settings;
59 import android.text.BoringLayout;
60 import android.text.DynamicLayout;
61 import android.text.Editable;
62 import android.text.GetChars;
63 import android.text.GraphicsOperations;
64 import android.text.InputFilter;
65 import android.text.InputType;
66 import android.text.Layout;
67 import android.text.ParcelableSpan;
68 import android.text.Selection;
69 import android.text.SpanWatcher;
70 import android.text.Spannable;
71 import android.text.SpannableString;
72 import android.text.SpannableStringBuilder;
73 import android.text.Spanned;
74 import android.text.SpannedString;
75 import android.text.StaticLayout;
76 import android.text.TextDirectionHeuristic;
77 import android.text.TextDirectionHeuristics;
78 import android.text.TextPaint;
79 import android.text.TextUtils;
80 import android.text.TextUtils.TruncateAt;
81 import android.text.TextWatcher;
82 import android.text.method.AllCapsTransformationMethod;
83 import android.text.method.ArrowKeyMovementMethod;
84 import android.text.method.DateKeyListener;
85 import android.text.method.DateTimeKeyListener;
86 import android.text.method.DialerKeyListener;
87 import android.text.method.DigitsKeyListener;
88 import android.text.method.KeyListener;
89 import android.text.method.LinkMovementMethod;
90 import android.text.method.MetaKeyKeyListener;
91 import android.text.method.MovementMethod;
92 import android.text.method.PasswordTransformationMethod;
93 import android.text.method.SingleLineTransformationMethod;
94 import android.text.method.TextKeyListener;
95 import android.text.method.TimeKeyListener;
96 import android.text.method.TransformationMethod;
97 import android.text.method.TransformationMethod2;
98 import android.text.method.WordIterator;
99 import android.text.style.CharacterStyle;
100 import android.text.style.ClickableSpan;
101 import android.text.style.ParagraphStyle;
102 import android.text.style.SpellCheckSpan;
103 import android.text.style.SuggestionSpan;
104 import android.text.style.URLSpan;
105 import android.text.style.UpdateAppearance;
106 import android.text.util.Linkify;
107 import android.util.AttributeSet;
108 import android.util.LocaleList;
109 import android.util.Log;
110 import android.util.TypedValue;
111 import android.view.AccessibilityIterators.TextSegmentIterator;
112 import android.view.ActionMode;
113 import android.view.Choreographer;
114 import android.view.ContextMenu;
115 import android.view.DragEvent;
116 import android.view.Gravity;
117 import android.view.HapticFeedbackConstants;
118 import android.view.KeyCharacterMap;
119 import android.view.KeyEvent;
120 import android.view.MotionEvent;
121 import android.view.PointerIcon;
122 import android.view.View;
123 import android.view.ViewConfiguration;
124 import android.view.ViewDebug;
125 import android.view.ViewGroup.LayoutParams;
126 import android.view.ViewHierarchyEncoder;
127 import android.view.ViewParent;
128 import android.view.ViewRootImpl;
129 import android.view.ViewStructure;
130 import android.view.ViewTreeObserver;
131 import android.view.accessibility.AccessibilityEvent;
132 import android.view.accessibility.AccessibilityManager;
133 import android.view.accessibility.AccessibilityNodeInfo;
134 import android.view.animation.AnimationUtils;
135 import android.view.inputmethod.BaseInputConnection;
136 import android.view.inputmethod.CompletionInfo;
137 import android.view.inputmethod.CorrectionInfo;
138 import android.view.inputmethod.EditorInfo;
139 import android.view.inputmethod.ExtractedText;
140 import android.view.inputmethod.ExtractedTextRequest;
141 import android.view.inputmethod.InputConnection;
142 import android.view.inputmethod.InputMethodManager;
143 import android.view.textservice.SpellCheckerSubtype;
144 import android.view.textservice.TextServicesManager;
145 import android.widget.RemoteViews.RemoteView;
147 import com.android.internal.annotations.VisibleForTesting;
148 import com.android.internal.util.FastMath;
149 import com.android.internal.widget.EditableInputConnection;
151 import org.xmlpull.v1.XmlPullParserException;
153 import java.io.IOException;
154 import java.lang.ref.WeakReference;
155 import java.util.ArrayList;
156 import java.util.Locale;
159 * Displays text to the user and optionally allows them to edit it. A TextView
160 * is a complete text editor, however the basic class is configured to not
161 * allow editing; see {@link EditText} for a subclass that configures the text
165 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
166 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
167 * android:textIsSelectable} to "true" or call
168 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
169 * allows users to make selection gestures in the TextView, which in turn triggers the system's
170 * built-in copy/paste controls.
172 * <b>XML attributes</b>
174 * See {@link android.R.styleable#TextView TextView Attributes},
175 * {@link android.R.styleable#View View Attributes}
177 * @attr ref android.R.styleable#TextView_text
178 * @attr ref android.R.styleable#TextView_bufferType
179 * @attr ref android.R.styleable#TextView_hint
180 * @attr ref android.R.styleable#TextView_textColor
181 * @attr ref android.R.styleable#TextView_textColorHighlight
182 * @attr ref android.R.styleable#TextView_textColorHint
183 * @attr ref android.R.styleable#TextView_textAppearance
184 * @attr ref android.R.styleable#TextView_textColorLink
185 * @attr ref android.R.styleable#TextView_textSize
186 * @attr ref android.R.styleable#TextView_textScaleX
187 * @attr ref android.R.styleable#TextView_fontFamily
188 * @attr ref android.R.styleable#TextView_typeface
189 * @attr ref android.R.styleable#TextView_textStyle
190 * @attr ref android.R.styleable#TextView_cursorVisible
191 * @attr ref android.R.styleable#TextView_maxLines
192 * @attr ref android.R.styleable#TextView_maxHeight
193 * @attr ref android.R.styleable#TextView_lines
194 * @attr ref android.R.styleable#TextView_height
195 * @attr ref android.R.styleable#TextView_minLines
196 * @attr ref android.R.styleable#TextView_minHeight
197 * @attr ref android.R.styleable#TextView_maxEms
198 * @attr ref android.R.styleable#TextView_maxWidth
199 * @attr ref android.R.styleable#TextView_ems
200 * @attr ref android.R.styleable#TextView_width
201 * @attr ref android.R.styleable#TextView_minEms
202 * @attr ref android.R.styleable#TextView_minWidth
203 * @attr ref android.R.styleable#TextView_gravity
204 * @attr ref android.R.styleable#TextView_scrollHorizontally
205 * @attr ref android.R.styleable#TextView_password
206 * @attr ref android.R.styleable#TextView_singleLine
207 * @attr ref android.R.styleable#TextView_selectAllOnFocus
208 * @attr ref android.R.styleable#TextView_includeFontPadding
209 * @attr ref android.R.styleable#TextView_maxLength
210 * @attr ref android.R.styleable#TextView_shadowColor
211 * @attr ref android.R.styleable#TextView_shadowDx
212 * @attr ref android.R.styleable#TextView_shadowDy
213 * @attr ref android.R.styleable#TextView_shadowRadius
214 * @attr ref android.R.styleable#TextView_autoLink
215 * @attr ref android.R.styleable#TextView_linksClickable
216 * @attr ref android.R.styleable#TextView_numeric
217 * @attr ref android.R.styleable#TextView_digits
218 * @attr ref android.R.styleable#TextView_phoneNumber
219 * @attr ref android.R.styleable#TextView_inputMethod
220 * @attr ref android.R.styleable#TextView_capitalize
221 * @attr ref android.R.styleable#TextView_autoText
222 * @attr ref android.R.styleable#TextView_editable
223 * @attr ref android.R.styleable#TextView_freezesText
224 * @attr ref android.R.styleable#TextView_ellipsize
225 * @attr ref android.R.styleable#TextView_drawableTop
226 * @attr ref android.R.styleable#TextView_drawableBottom
227 * @attr ref android.R.styleable#TextView_drawableRight
228 * @attr ref android.R.styleable#TextView_drawableLeft
229 * @attr ref android.R.styleable#TextView_drawableStart
230 * @attr ref android.R.styleable#TextView_drawableEnd
231 * @attr ref android.R.styleable#TextView_drawablePadding
232 * @attr ref android.R.styleable#TextView_drawableTint
233 * @attr ref android.R.styleable#TextView_drawableTintMode
234 * @attr ref android.R.styleable#TextView_lineSpacingExtra
235 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
236 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
237 * @attr ref android.R.styleable#TextView_inputType
238 * @attr ref android.R.styleable#TextView_imeOptions
239 * @attr ref android.R.styleable#TextView_privateImeOptions
240 * @attr ref android.R.styleable#TextView_imeActionLabel
241 * @attr ref android.R.styleable#TextView_imeActionId
242 * @attr ref android.R.styleable#TextView_editorExtras
243 * @attr ref android.R.styleable#TextView_elegantTextHeight
244 * @attr ref android.R.styleable#TextView_letterSpacing
245 * @attr ref android.R.styleable#TextView_fontFeatureSettings
246 * @attr ref android.R.styleable#TextView_breakStrategy
247 * @attr ref android.R.styleable#TextView_hyphenationFrequency
250 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
251 static final String LOG_TAG = "TextView";
252 static final boolean DEBUG_EXTRACT = false;
254 // Enum for the "typeface" XML parameter.
255 // TODO: How can we get this from the XML instead of hardcoding it here?
256 private static final int SANS = 1;
257 private static final int SERIF = 2;
258 private static final int MONOSPACE = 3;
260 // Bitfield for the "numeric" XML parameter.
261 // TODO: How can we get this from the XML instead of hardcoding it here?
262 private static final int SIGNED = 2;
263 private static final int DECIMAL = 4;
266 * Draw marquee text with fading edges as usual
268 private static final int MARQUEE_FADE_NORMAL = 0;
271 * Draw marquee text as ellipsize end while inactive instead of with the fade.
272 * (Useful for devices where the fade can be expensive if overdone)
274 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
277 * Draw marquee text with fading edges because it is currently active/animating.
279 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
281 private static final int LINES = 1;
282 private static final int EMS = LINES;
283 private static final int PIXELS = 2;
285 private static final RectF TEMP_RECTF = new RectF();
287 // XXX should be much larger
288 private static final int VERY_WIDE = 1024*1024;
289 private static final int ANIMATED_SCROLL_GAP = 250;
291 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
292 private static final Spanned EMPTY_SPANNED = new SpannedString("");
294 private static final int CHANGE_WATCHER_PRIORITY = 100;
296 // New state used to change background based on whether this TextView is multiline.
297 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
299 // Accessibility action to share selected text.
300 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
305 // Accessibility action start id for "process text" actions.
306 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
311 static final int PROCESS_TEXT_REQUEST_CODE = 100;
314 * Return code of {@link #doKeyDown}.
316 private static final int KEY_EVENT_NOT_HANDLED = 0;
317 private static final int KEY_EVENT_HANDLED = -1;
318 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
319 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
321 // System wide time for last cut, copy or text changed action.
322 static long sLastCutCopyOrTextChangedTime;
324 private ColorStateList mTextColor;
325 private ColorStateList mHintTextColor;
326 private ColorStateList mLinkTextColor;
327 @ViewDebug.ExportedProperty(category = "text")
328 private int mCurTextColor;
329 private int mCurHintTextColor;
330 private boolean mFreezesText;
331 private boolean mDispatchTemporaryDetach;
333 /** Whether this view is temporarily detached from the parent view. */
334 boolean mTemporaryDetach;
336 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
337 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
339 private float mShadowRadius, mShadowDx, mShadowDy;
340 private int mShadowColor;
342 private boolean mPreDrawRegistered;
343 private boolean mPreDrawListenerDetached;
345 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
346 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
347 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
348 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
349 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
350 private boolean mPreventDefaultMovement;
352 private TextUtils.TruncateAt mEllipsize;
354 static class Drawables {
355 static final int LEFT = 0;
356 static final int TOP = 1;
357 static final int RIGHT = 2;
358 static final int BOTTOM = 3;
360 static final int DRAWABLE_NONE = -1;
361 static final int DRAWABLE_RIGHT = 0;
362 static final int DRAWABLE_LEFT = 1;
364 final Rect mCompoundRect = new Rect();
366 final Drawable[] mShowing = new Drawable[4];
368 ColorStateList mTintList;
369 PorterDuff.Mode mTintMode;
371 boolean mHasTintMode;
373 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
374 Drawable mDrawableLeftInitial, mDrawableRightInitial;
376 boolean mIsRtlCompatibilityMode;
379 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
380 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
382 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
383 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
385 int mDrawablePadding;
387 int mDrawableSaved = DRAWABLE_NONE;
389 public Drawables(Context context) {
390 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
391 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
392 !context.getApplicationInfo().hasRtlSupport());
397 * @return {@code true} if this object contains metadata that needs to
398 * be retained, {@code false} otherwise
400 public boolean hasMetadata() {
401 return mDrawablePadding != 0 || mHasTintMode || mHasTint;
405 * Updates the list of displayed drawables to account for the current
408 * @param layoutDirection the current layout direction
409 * @return {@code true} if the displayed drawables changed
411 public boolean resolveWithLayoutDirection(int layoutDirection) {
412 final Drawable previousLeft = mShowing[Drawables.LEFT];
413 final Drawable previousRight = mShowing[Drawables.RIGHT];
415 // First reset "left" and "right" drawables to their initial values
416 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
417 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
419 if (mIsRtlCompatibilityMode) {
420 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
421 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
422 mShowing[Drawables.LEFT] = mDrawableStart;
423 mDrawableSizeLeft = mDrawableSizeStart;
424 mDrawableHeightLeft = mDrawableHeightStart;
426 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
427 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
428 mShowing[Drawables.RIGHT] = mDrawableEnd;
429 mDrawableSizeRight = mDrawableSizeEnd;
430 mDrawableHeightRight = mDrawableHeightEnd;
433 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
434 // drawable if and only if they have been defined
435 switch(layoutDirection) {
436 case LAYOUT_DIRECTION_RTL:
438 mShowing[Drawables.RIGHT] = mDrawableStart;
439 mDrawableSizeRight = mDrawableSizeStart;
440 mDrawableHeightRight = mDrawableHeightStart;
442 mShowing[Drawables.LEFT] = mDrawableEnd;
443 mDrawableSizeLeft = mDrawableSizeEnd;
444 mDrawableHeightLeft = mDrawableHeightEnd;
448 case LAYOUT_DIRECTION_LTR:
451 mShowing[Drawables.LEFT] = mDrawableStart;
452 mDrawableSizeLeft = mDrawableSizeStart;
453 mDrawableHeightLeft = mDrawableHeightStart;
455 mShowing[Drawables.RIGHT] = mDrawableEnd;
456 mDrawableSizeRight = mDrawableSizeEnd;
457 mDrawableHeightRight = mDrawableHeightEnd;
463 applyErrorDrawableIfNeeded(layoutDirection);
465 return mShowing[Drawables.LEFT] != previousLeft
466 || mShowing[Drawables.RIGHT] != previousRight;
469 public void setErrorDrawable(Drawable dr, TextView tv) {
470 if (mDrawableError != dr && mDrawableError != null) {
471 mDrawableError.setCallback(null);
475 if (mDrawableError != null) {
476 final Rect compoundRect = mCompoundRect;
477 final int[] state = tv.getDrawableState();
479 mDrawableError.setState(state);
480 mDrawableError.copyBounds(compoundRect);
481 mDrawableError.setCallback(tv);
482 mDrawableSizeError = compoundRect.width();
483 mDrawableHeightError = compoundRect.height();
485 mDrawableSizeError = mDrawableHeightError = 0;
489 private void applyErrorDrawableIfNeeded(int layoutDirection) {
490 // first restore the initial state if needed
491 switch (mDrawableSaved) {
493 mShowing[Drawables.LEFT] = mDrawableTemp;
494 mDrawableSizeLeft = mDrawableSizeTemp;
495 mDrawableHeightLeft = mDrawableHeightTemp;
498 mShowing[Drawables.RIGHT] = mDrawableTemp;
499 mDrawableSizeRight = mDrawableSizeTemp;
500 mDrawableHeightRight = mDrawableHeightTemp;
505 // then, if needed, assign the Error drawable to the correct location
506 if (mDrawableError != null) {
507 switch(layoutDirection) {
508 case LAYOUT_DIRECTION_RTL:
509 mDrawableSaved = DRAWABLE_LEFT;
511 mDrawableTemp = mShowing[Drawables.LEFT];
512 mDrawableSizeTemp = mDrawableSizeLeft;
513 mDrawableHeightTemp = mDrawableHeightLeft;
515 mShowing[Drawables.LEFT] = mDrawableError;
516 mDrawableSizeLeft = mDrawableSizeError;
517 mDrawableHeightLeft = mDrawableHeightError;
519 case LAYOUT_DIRECTION_LTR:
521 mDrawableSaved = DRAWABLE_RIGHT;
523 mDrawableTemp = mShowing[Drawables.RIGHT];
524 mDrawableSizeTemp = mDrawableSizeRight;
525 mDrawableHeightTemp = mDrawableHeightRight;
527 mShowing[Drawables.RIGHT] = mDrawableError;
528 mDrawableSizeRight = mDrawableSizeError;
529 mDrawableHeightRight = mDrawableHeightError;
536 Drawables mDrawables;
538 private CharWrapper mCharWrapper;
540 private Marquee mMarquee;
541 private boolean mRestartMarquee;
543 private int mMarqueeRepeatLimit = 3;
545 private int mLastLayoutDirection = -1;
548 * On some devices the fading edges add a performance penalty if used
549 * extensively in the same layout. This mode indicates how the marquee
550 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
552 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
555 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
556 * the layout that should be used when the mode switches.
558 private Layout mSavedMarqueeModeLayout;
560 @ViewDebug.ExportedProperty(category = "text")
561 private CharSequence mText;
562 private CharSequence mTransformed;
563 private BufferType mBufferType = BufferType.NORMAL;
565 private CharSequence mHint;
566 private Layout mHintLayout;
568 private MovementMethod mMovement;
570 private TransformationMethod mTransformation;
571 private boolean mAllowTransformationLengthChange;
572 private ChangeWatcher mChangeWatcher;
574 private ArrayList<TextWatcher> mListeners;
576 // display attributes
577 private final TextPaint mTextPaint;
578 private boolean mUserSetTextScaleX;
579 private Layout mLayout;
580 private boolean mLocalesChanged = false;
582 @ViewDebug.ExportedProperty(category = "text")
583 private int mGravity = Gravity.TOP | Gravity.START;
584 private boolean mHorizontallyScrolling;
586 private int mAutoLinkMask;
587 private boolean mLinksClickable = true;
589 private float mSpacingMult = 1.0f;
590 private float mSpacingAdd = 0.0f;
592 private int mBreakStrategy;
593 private int mHyphenationFrequency;
595 private int mMaximum = Integer.MAX_VALUE;
596 private int mMaxMode = LINES;
597 private int mMinimum = 0;
598 private int mMinMode = LINES;
600 private int mOldMaximum = mMaximum;
601 private int mOldMaxMode = mMaxMode;
603 private int mMaxWidth = Integer.MAX_VALUE;
604 private int mMaxWidthMode = PIXELS;
605 private int mMinWidth = 0;
606 private int mMinWidthMode = PIXELS;
608 private boolean mSingleLine;
609 private int mDesiredHeightAtMeasure = -1;
610 private boolean mIncludePad = true;
611 private int mDeferScroll = -1;
613 // tmp primitives, so we don't alloc them on each draw
614 private Rect mTempRect;
615 private long mLastScroll;
616 private Scroller mScroller;
618 private BoringLayout.Metrics mBoring, mHintBoring;
619 private BoringLayout mSavedLayout, mSavedHintLayout;
621 private TextDirectionHeuristic mTextDir;
623 private InputFilter[] mFilters = NO_FILTERS;
625 private volatile Locale mCurrentSpellCheckerLocaleCache;
627 // It is possible to have a selection even when mEditor is null (programmatically set, like when
628 // a link is pressed). These highlight-related fields do not go in mEditor.
629 int mHighlightColor = 0x6633B5E5;
630 private Path mHighlightPath;
631 private final Paint mHighlightPaint;
632 private boolean mHighlightPathBogus = true;
634 // Although these fields are specific to editable text, they are not added to Editor because
635 // they are defined by the TextView's style and are theme-dependent.
636 int mCursorDrawableRes;
637 // These six fields, could be moved to Editor, since we know their default values and we
638 // could condition the creation of the Editor to a non standard value. This is however
639 // brittle since the hardcoded values here (such as
640 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
641 // default style is modified.
642 int mTextSelectHandleLeftRes;
643 int mTextSelectHandleRightRes;
644 int mTextSelectHandleRes;
645 int mTextEditSuggestionItemLayout;
646 int mTextEditSuggestionContainerLayout;
647 int mTextEditSuggestionHighlightStyle;
650 * EditText specific data, created on demand when one of the Editor fields is used.
651 * See {@link #createEditorIfNeeded()}.
653 private Editor mEditor;
655 private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
656 private static final int DEVICE_PROVISIONED_NO = 1;
657 private static final int DEVICE_PROVISIONED_YES = 2;
660 * Some special options such as sharing selected text should only be shown if the device
661 * is provisioned. Only check the provisioned state once for a given view instance.
663 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
666 * Kick-start the font cache for the zygote process (to pay the cost of
667 * initializing freetype for our default font only once).
670 Paint p = new Paint();
671 p.setAntiAlias(true);
672 // We don't care about the result, just the side-effect of measuring.
677 * Interface definition for a callback to be invoked when an action is
678 * performed on the editor.
680 public interface OnEditorActionListener {
682 * Called when an action is being performed.
684 * @param v The view that was clicked.
685 * @param actionId Identifier of the action. This will be either the
686 * identifier you supplied, or {@link EditorInfo#IME_NULL
687 * EditorInfo.IME_NULL} if being called due to the enter key
689 * @param event If triggered by an enter key, this is the event;
690 * otherwise, this is null.
691 * @return Return true if you have consumed the action, else false.
693 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
696 public TextView(Context context) {
700 public TextView(Context context, @Nullable AttributeSet attrs) {
701 this(context, attrs, com.android.internal.R.attr.textViewStyle);
704 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
705 this(context, attrs, defStyleAttr, 0);
708 @SuppressWarnings("deprecation")
710 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
711 super(context, attrs, defStyleAttr, defStyleRes);
715 final Resources res = getResources();
716 final CompatibilityInfo compat = res.getCompatibilityInfo();
718 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
719 mTextPaint.density = res.getDisplayMetrics().density;
720 mTextPaint.setCompatibilityScaling(compat.applicationScale);
722 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
723 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
725 mMovement = getDefaultMovementMethod();
727 mTransformation = null;
729 int textColorHighlight = 0;
730 ColorStateList textColor = null;
731 ColorStateList textColorHint = null;
732 ColorStateList textColorLink = null;
734 String fontFamily = null;
735 boolean fontFamilyExplicit = false;
736 int typefaceIndex = -1;
738 boolean allCaps = false;
740 float dx = 0, dy = 0, r = 0;
741 boolean elegant = false;
742 float letterSpacing = 0;
743 String fontFeatureSettings = null;
744 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
745 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
747 final Resources.Theme theme = context.getTheme();
750 * Look the appearance up without checking first if it exists because
751 * almost every TextView has one and it greatly simplifies the logic
752 * to be able to parse the appearance first and then let specific tags
753 * for this View override it.
755 TypedArray a = theme.obtainStyledAttributes(attrs,
756 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
757 TypedArray appearance = null;
758 int ap = a.getResourceId(
759 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
762 appearance = theme.obtainStyledAttributes(
763 ap, com.android.internal.R.styleable.TextAppearance);
765 if (appearance != null) {
766 int n = appearance.getIndexCount();
767 for (int i = 0; i < n; i++) {
768 int attr = appearance.getIndex(i);
771 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
772 textColorHighlight = appearance.getColor(attr, textColorHighlight);
775 case com.android.internal.R.styleable.TextAppearance_textColor:
776 textColor = appearance.getColorStateList(attr);
779 case com.android.internal.R.styleable.TextAppearance_textColorHint:
780 textColorHint = appearance.getColorStateList(attr);
783 case com.android.internal.R.styleable.TextAppearance_textColorLink:
784 textColorLink = appearance.getColorStateList(attr);
787 case com.android.internal.R.styleable.TextAppearance_textSize:
788 textSize = appearance.getDimensionPixelSize(attr, textSize);
791 case com.android.internal.R.styleable.TextAppearance_typeface:
792 typefaceIndex = appearance.getInt(attr, -1);
795 case com.android.internal.R.styleable.TextAppearance_fontFamily:
796 fontFamily = appearance.getString(attr);
799 case com.android.internal.R.styleable.TextAppearance_textStyle:
800 styleIndex = appearance.getInt(attr, -1);
803 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
804 allCaps = appearance.getBoolean(attr, false);
807 case com.android.internal.R.styleable.TextAppearance_shadowColor:
808 shadowcolor = appearance.getInt(attr, 0);
811 case com.android.internal.R.styleable.TextAppearance_shadowDx:
812 dx = appearance.getFloat(attr, 0);
815 case com.android.internal.R.styleable.TextAppearance_shadowDy:
816 dy = appearance.getFloat(attr, 0);
819 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
820 r = appearance.getFloat(attr, 0);
823 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
824 elegant = appearance.getBoolean(attr, false);
827 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
828 letterSpacing = appearance.getFloat(attr, 0);
831 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
832 fontFeatureSettings = appearance.getString(attr);
837 appearance.recycle();
840 boolean editable = getDefaultEditable();
841 CharSequence inputMethod = null;
843 CharSequence digits = null;
844 boolean phone = false;
845 boolean autotext = false;
848 boolean selectallonfocus = false;
849 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
850 drawableBottom = null, drawableStart = null, drawableEnd = null;
851 ColorStateList drawableTint = null;
852 PorterDuff.Mode drawableTintMode = null;
853 int drawablePadding = 0;
855 boolean singleLine = false;
857 CharSequence text = "";
858 CharSequence hint = null;
859 boolean password = false;
860 int inputType = EditorInfo.TYPE_NULL;
862 a = theme.obtainStyledAttributes(
863 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
865 int n = a.getIndexCount();
866 for (int i = 0; i < n; i++) {
867 int attr = a.getIndex(i);
870 case com.android.internal.R.styleable.TextView_editable:
871 editable = a.getBoolean(attr, editable);
874 case com.android.internal.R.styleable.TextView_inputMethod:
875 inputMethod = a.getText(attr);
878 case com.android.internal.R.styleable.TextView_numeric:
879 numeric = a.getInt(attr, numeric);
882 case com.android.internal.R.styleable.TextView_digits:
883 digits = a.getText(attr);
886 case com.android.internal.R.styleable.TextView_phoneNumber:
887 phone = a.getBoolean(attr, phone);
890 case com.android.internal.R.styleable.TextView_autoText:
891 autotext = a.getBoolean(attr, autotext);
894 case com.android.internal.R.styleable.TextView_capitalize:
895 autocap = a.getInt(attr, autocap);
898 case com.android.internal.R.styleable.TextView_bufferType:
899 buffertype = a.getInt(attr, buffertype);
902 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
903 selectallonfocus = a.getBoolean(attr, selectallonfocus);
906 case com.android.internal.R.styleable.TextView_autoLink:
907 mAutoLinkMask = a.getInt(attr, 0);
910 case com.android.internal.R.styleable.TextView_linksClickable:
911 mLinksClickable = a.getBoolean(attr, true);
914 case com.android.internal.R.styleable.TextView_drawableLeft:
915 drawableLeft = a.getDrawable(attr);
918 case com.android.internal.R.styleable.TextView_drawableTop:
919 drawableTop = a.getDrawable(attr);
922 case com.android.internal.R.styleable.TextView_drawableRight:
923 drawableRight = a.getDrawable(attr);
926 case com.android.internal.R.styleable.TextView_drawableBottom:
927 drawableBottom = a.getDrawable(attr);
930 case com.android.internal.R.styleable.TextView_drawableStart:
931 drawableStart = a.getDrawable(attr);
934 case com.android.internal.R.styleable.TextView_drawableEnd:
935 drawableEnd = a.getDrawable(attr);
938 case com.android.internal.R.styleable.TextView_drawableTint:
939 drawableTint = a.getColorStateList(attr);
942 case com.android.internal.R.styleable.TextView_drawableTintMode:
943 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
946 case com.android.internal.R.styleable.TextView_drawablePadding:
947 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
950 case com.android.internal.R.styleable.TextView_maxLines:
951 setMaxLines(a.getInt(attr, -1));
954 case com.android.internal.R.styleable.TextView_maxHeight:
955 setMaxHeight(a.getDimensionPixelSize(attr, -1));
958 case com.android.internal.R.styleable.TextView_lines:
959 setLines(a.getInt(attr, -1));
962 case com.android.internal.R.styleable.TextView_height:
963 setHeight(a.getDimensionPixelSize(attr, -1));
966 case com.android.internal.R.styleable.TextView_minLines:
967 setMinLines(a.getInt(attr, -1));
970 case com.android.internal.R.styleable.TextView_minHeight:
971 setMinHeight(a.getDimensionPixelSize(attr, -1));
974 case com.android.internal.R.styleable.TextView_maxEms:
975 setMaxEms(a.getInt(attr, -1));
978 case com.android.internal.R.styleable.TextView_maxWidth:
979 setMaxWidth(a.getDimensionPixelSize(attr, -1));
982 case com.android.internal.R.styleable.TextView_ems:
983 setEms(a.getInt(attr, -1));
986 case com.android.internal.R.styleable.TextView_width:
987 setWidth(a.getDimensionPixelSize(attr, -1));
990 case com.android.internal.R.styleable.TextView_minEms:
991 setMinEms(a.getInt(attr, -1));
994 case com.android.internal.R.styleable.TextView_minWidth:
995 setMinWidth(a.getDimensionPixelSize(attr, -1));
998 case com.android.internal.R.styleable.TextView_gravity:
999 setGravity(a.getInt(attr, -1));
1002 case com.android.internal.R.styleable.TextView_hint:
1003 hint = a.getText(attr);
1006 case com.android.internal.R.styleable.TextView_text:
1007 text = a.getText(attr);
1010 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1011 if (a.getBoolean(attr, false)) {
1012 setHorizontallyScrolling(true);
1016 case com.android.internal.R.styleable.TextView_singleLine:
1017 singleLine = a.getBoolean(attr, singleLine);
1020 case com.android.internal.R.styleable.TextView_ellipsize:
1021 ellipsize = a.getInt(attr, ellipsize);
1024 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1025 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1028 case com.android.internal.R.styleable.TextView_includeFontPadding:
1029 if (!a.getBoolean(attr, true)) {
1030 setIncludeFontPadding(false);
1034 case com.android.internal.R.styleable.TextView_cursorVisible:
1035 if (!a.getBoolean(attr, true)) {
1036 setCursorVisible(false);
1040 case com.android.internal.R.styleable.TextView_maxLength:
1041 maxlength = a.getInt(attr, -1);
1044 case com.android.internal.R.styleable.TextView_textScaleX:
1045 setTextScaleX(a.getFloat(attr, 1.0f));
1048 case com.android.internal.R.styleable.TextView_freezesText:
1049 mFreezesText = a.getBoolean(attr, false);
1052 case com.android.internal.R.styleable.TextView_shadowColor:
1053 shadowcolor = a.getInt(attr, 0);
1056 case com.android.internal.R.styleable.TextView_shadowDx:
1057 dx = a.getFloat(attr, 0);
1060 case com.android.internal.R.styleable.TextView_shadowDy:
1061 dy = a.getFloat(attr, 0);
1064 case com.android.internal.R.styleable.TextView_shadowRadius:
1065 r = a.getFloat(attr, 0);
1068 case com.android.internal.R.styleable.TextView_enabled:
1069 setEnabled(a.getBoolean(attr, isEnabled()));
1072 case com.android.internal.R.styleable.TextView_textColorHighlight:
1073 textColorHighlight = a.getColor(attr, textColorHighlight);
1076 case com.android.internal.R.styleable.TextView_textColor:
1077 textColor = a.getColorStateList(attr);
1080 case com.android.internal.R.styleable.TextView_textColorHint:
1081 textColorHint = a.getColorStateList(attr);
1084 case com.android.internal.R.styleable.TextView_textColorLink:
1085 textColorLink = a.getColorStateList(attr);
1088 case com.android.internal.R.styleable.TextView_textSize:
1089 textSize = a.getDimensionPixelSize(attr, textSize);
1092 case com.android.internal.R.styleable.TextView_typeface:
1093 typefaceIndex = a.getInt(attr, typefaceIndex);
1096 case com.android.internal.R.styleable.TextView_textStyle:
1097 styleIndex = a.getInt(attr, styleIndex);
1100 case com.android.internal.R.styleable.TextView_fontFamily:
1101 fontFamily = a.getString(attr);
1102 fontFamilyExplicit = true;
1105 case com.android.internal.R.styleable.TextView_password:
1106 password = a.getBoolean(attr, password);
1109 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1110 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1113 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1114 mSpacingMult = a.getFloat(attr, mSpacingMult);
1117 case com.android.internal.R.styleable.TextView_inputType:
1118 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1121 case com.android.internal.R.styleable.TextView_allowUndo:
1122 createEditorIfNeeded();
1123 mEditor.mAllowUndo = a.getBoolean(attr, true);
1126 case com.android.internal.R.styleable.TextView_imeOptions:
1127 createEditorIfNeeded();
1128 mEditor.createInputContentTypeIfNeeded();
1129 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1130 mEditor.mInputContentType.imeOptions);
1133 case com.android.internal.R.styleable.TextView_imeActionLabel:
1134 createEditorIfNeeded();
1135 mEditor.createInputContentTypeIfNeeded();
1136 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1139 case com.android.internal.R.styleable.TextView_imeActionId:
1140 createEditorIfNeeded();
1141 mEditor.createInputContentTypeIfNeeded();
1142 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1143 mEditor.mInputContentType.imeActionId);
1146 case com.android.internal.R.styleable.TextView_privateImeOptions:
1147 setPrivateImeOptions(a.getString(attr));
1150 case com.android.internal.R.styleable.TextView_editorExtras:
1152 setInputExtras(a.getResourceId(attr, 0));
1153 } catch (XmlPullParserException e) {
1154 Log.w(LOG_TAG, "Failure reading input extras", e);
1155 } catch (IOException e) {
1156 Log.w(LOG_TAG, "Failure reading input extras", e);
1160 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1161 mCursorDrawableRes = a.getResourceId(attr, 0);
1164 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1165 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1168 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1169 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1172 case com.android.internal.R.styleable.TextView_textSelectHandle:
1173 mTextSelectHandleRes = a.getResourceId(attr, 0);
1176 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1177 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1180 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1181 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1184 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1185 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1188 case com.android.internal.R.styleable.TextView_textIsSelectable:
1189 setTextIsSelectable(a.getBoolean(attr, false));
1192 case com.android.internal.R.styleable.TextView_textAllCaps:
1193 allCaps = a.getBoolean(attr, false);
1196 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1197 elegant = a.getBoolean(attr, false);
1200 case com.android.internal.R.styleable.TextView_letterSpacing:
1201 letterSpacing = a.getFloat(attr, 0);
1204 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1205 fontFeatureSettings = a.getString(attr);
1208 case com.android.internal.R.styleable.TextView_breakStrategy:
1209 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1212 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1213 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1219 BufferType bufferType = BufferType.EDITABLE;
1221 final int variation =
1222 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1223 final boolean passwordInputType = variation
1224 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1225 final boolean webPasswordInputType = variation
1226 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1227 final boolean numberPasswordInputType = variation
1228 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1230 if (inputMethod != null) {
1234 c = Class.forName(inputMethod.toString());
1235 } catch (ClassNotFoundException ex) {
1236 throw new RuntimeException(ex);
1240 createEditorIfNeeded();
1241 mEditor.mKeyListener = (KeyListener) c.newInstance();
1242 } catch (InstantiationException ex) {
1243 throw new RuntimeException(ex);
1244 } catch (IllegalAccessException ex) {
1245 throw new RuntimeException(ex);
1248 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1250 : mEditor.mKeyListener.getInputType();
1251 } catch (IncompatibleClassChangeError e) {
1252 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1254 } else if (digits != null) {
1255 createEditorIfNeeded();
1256 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1257 // If no input type was specified, we will default to generic
1258 // text, since we can't tell the IME about the set of digits
1259 // that was selected.
1260 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1261 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1262 } else if (inputType != EditorInfo.TYPE_NULL) {
1263 setInputType(inputType, true);
1264 // If set, the input type overrides what was set using the deprecated singleLine flag.
1265 singleLine = !isMultilineInputType(inputType);
1267 createEditorIfNeeded();
1268 mEditor.mKeyListener = DialerKeyListener.getInstance();
1269 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1270 } else if (numeric != 0) {
1271 createEditorIfNeeded();
1272 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1273 (numeric & DECIMAL) != 0);
1274 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1275 if ((numeric & SIGNED) != 0) {
1276 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1278 if ((numeric & DECIMAL) != 0) {
1279 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1281 mEditor.mInputType = inputType;
1282 } else if (autotext || autocap != -1) {
1283 TextKeyListener.Capitalize cap;
1285 inputType = EditorInfo.TYPE_CLASS_TEXT;
1289 cap = TextKeyListener.Capitalize.SENTENCES;
1290 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1294 cap = TextKeyListener.Capitalize.WORDS;
1295 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1299 cap = TextKeyListener.Capitalize.CHARACTERS;
1300 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1304 cap = TextKeyListener.Capitalize.NONE;
1308 createEditorIfNeeded();
1309 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1310 mEditor.mInputType = inputType;
1311 } else if (isTextSelectable()) {
1312 // Prevent text changes from keyboard.
1313 if (mEditor != null) {
1314 mEditor.mKeyListener = null;
1315 mEditor.mInputType = EditorInfo.TYPE_NULL;
1317 bufferType = BufferType.SPANNABLE;
1318 // So that selection can be changed using arrow keys and touch is handled.
1319 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1320 } else if (editable) {
1321 createEditorIfNeeded();
1322 mEditor.mKeyListener = TextKeyListener.getInstance();
1323 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1325 if (mEditor != null) mEditor.mKeyListener = null;
1327 switch (buffertype) {
1329 bufferType = BufferType.NORMAL;
1332 bufferType = BufferType.SPANNABLE;
1335 bufferType = BufferType.EDITABLE;
1340 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1341 webPasswordInputType, numberPasswordInputType);
1343 if (selectallonfocus) {
1344 createEditorIfNeeded();
1345 mEditor.mSelectAllOnFocus = true;
1347 if (bufferType == BufferType.NORMAL)
1348 bufferType = BufferType.SPANNABLE;
1351 // Set up the tint (if needed) before setting the drawables so that it
1352 // gets applied correctly.
1353 if (drawableTint != null || drawableTintMode != null) {
1354 if (mDrawables == null) {
1355 mDrawables = new Drawables(context);
1357 if (drawableTint != null) {
1358 mDrawables.mTintList = drawableTint;
1359 mDrawables.mHasTint = true;
1361 if (drawableTintMode != null) {
1362 mDrawables.mTintMode = drawableTintMode;
1363 mDrawables.mHasTintMode = true;
1367 // This call will save the initial left/right drawables
1368 setCompoundDrawablesWithIntrinsicBounds(
1369 drawableLeft, drawableTop, drawableRight, drawableBottom);
1370 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1371 setCompoundDrawablePadding(drawablePadding);
1373 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1374 // of lines of height are unchanged for multi-line TextViews.
1375 setInputTypeSingleLine(singleLine);
1376 applySingleLine(singleLine, singleLine, singleLine);
1378 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1379 ellipsize = 3; // END
1382 switch (ellipsize) {
1384 setEllipsize(TextUtils.TruncateAt.START);
1387 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1390 setEllipsize(TextUtils.TruncateAt.END);
1393 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1394 setHorizontalFadingEdgeEnabled(true);
1395 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1397 setHorizontalFadingEdgeEnabled(false);
1398 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1400 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1404 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1405 setHintTextColor(textColorHint);
1406 setLinkTextColor(textColorLink);
1407 if (textColorHighlight != 0) {
1408 setHighlightColor(textColorHighlight);
1410 setRawTextSize(textSize);
1411 setElegantTextHeight(elegant);
1412 setLetterSpacing(letterSpacing);
1413 setFontFeatureSettings(fontFeatureSettings);
1416 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1419 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1420 setTransformationMethod(PasswordTransformationMethod.getInstance());
1421 typefaceIndex = MONOSPACE;
1422 } else if (mEditor != null &&
1423 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1424 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1425 typefaceIndex = MONOSPACE;
1428 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1431 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1433 if (shadowcolor != 0) {
1434 setShadowLayer(r, dx, dy, shadowcolor);
1437 if (maxlength >= 0) {
1438 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1440 setFilters(NO_FILTERS);
1443 setText(text, bufferType);
1444 if (hint != null) setHint(hint);
1447 * Views are not normally focusable unless specified to be.
1448 * However, TextViews that have input or movement methods *are*
1449 * focusable by default.
1451 a = context.obtainStyledAttributes(
1452 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1454 boolean focusable = mMovement != null || getKeyListener() != null;
1455 boolean clickable = focusable || isClickable();
1456 boolean longClickable = focusable || isLongClickable();
1458 n = a.getIndexCount();
1459 for (int i = 0; i < n; i++) {
1460 int attr = a.getIndex(i);
1463 case com.android.internal.R.styleable.View_focusable:
1464 focusable = a.getBoolean(attr, focusable);
1467 case com.android.internal.R.styleable.View_clickable:
1468 clickable = a.getBoolean(attr, clickable);
1471 case com.android.internal.R.styleable.View_longClickable:
1472 longClickable = a.getBoolean(attr, longClickable);
1478 setFocusable(focusable);
1479 setClickable(clickable);
1480 setLongClickable(longClickable);
1482 if (mEditor != null) mEditor.prepareCursorControllers();
1484 // If not explicitly specified this view is important for accessibility.
1485 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1486 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1490 private int[] parseDimensionArray(TypedArray dimens) {
1491 if (dimens == null) {
1494 int[] result = new int[dimens.length()];
1495 for (int i = 0; i < result.length; i++) {
1496 result[i] = dimens.getDimensionPixelSize(i, 0);
1505 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1506 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1507 if (resultCode == Activity.RESULT_OK && data != null) {
1508 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1509 if (result != null) {
1510 if (isTextEditable()) {
1511 replaceSelectionWithText(result);
1513 if (result.length() > 0) {
1514 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1519 } else if (mText instanceof Spannable) {
1520 // Reset the selection.
1521 stopTextActionMode();
1522 Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
1525 if (mEditor.hasSelectionController()) {
1526 mEditor.startSelectionActionMode();
1531 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1533 if (familyName != null) {
1534 tf = Typeface.create(familyName, styleIndex);
1540 switch (typefaceIndex) {
1542 tf = Typeface.SANS_SERIF;
1546 tf = Typeface.SERIF;
1550 tf = Typeface.MONOSPACE;
1554 setTypeface(tf, styleIndex);
1557 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1558 boolean hasRelativeDrawables = (start != null) || (end != null);
1559 if (hasRelativeDrawables) {
1560 Drawables dr = mDrawables;
1562 mDrawables = dr = new Drawables(getContext());
1564 mDrawables.mOverride = true;
1565 final Rect compoundRect = dr.mCompoundRect;
1566 int[] state = getDrawableState();
1567 if (start != null) {
1568 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1569 start.setState(state);
1570 start.copyBounds(compoundRect);
1571 start.setCallback(this);
1573 dr.mDrawableStart = start;
1574 dr.mDrawableSizeStart = compoundRect.width();
1575 dr.mDrawableHeightStart = compoundRect.height();
1577 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1580 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1581 end.setState(state);
1582 end.copyBounds(compoundRect);
1583 end.setCallback(this);
1585 dr.mDrawableEnd = end;
1586 dr.mDrawableSizeEnd = compoundRect.width();
1587 dr.mDrawableHeightEnd = compoundRect.height();
1589 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1591 resetResolvedDrawables();
1593 applyCompoundDrawableTint();
1598 public void setEnabled(boolean enabled) {
1599 if (enabled == isEnabled()) {
1604 // Hide the soft input if the currently active TextView is disabled
1605 InputMethodManager imm = InputMethodManager.peekInstance();
1606 if (imm != null && imm.isActive(this)) {
1607 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1611 super.setEnabled(enabled);
1614 // Make sure IME is updated with current editor info.
1615 InputMethodManager imm = InputMethodManager.peekInstance();
1616 if (imm != null) imm.restartInput(this);
1619 // Will change text color
1620 if (mEditor != null) {
1621 mEditor.invalidateTextDisplayList();
1622 mEditor.prepareCursorControllers();
1624 // start or stop the cursor blinking as appropriate
1625 mEditor.makeBlink();
1630 * Sets the typeface and style in which the text should be displayed,
1631 * and turns on the fake bold and italic bits in the Paint if the
1632 * Typeface that you provided does not have all the bits in the
1633 * style that you specified.
1635 * @attr ref android.R.styleable#TextView_typeface
1636 * @attr ref android.R.styleable#TextView_textStyle
1638 public void setTypeface(Typeface tf, int style) {
1641 tf = Typeface.defaultFromStyle(style);
1643 tf = Typeface.create(tf, style);
1647 // now compute what (if any) algorithmic styling is needed
1648 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1649 int need = style & ~typefaceStyle;
1650 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1651 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1653 mTextPaint.setFakeBoldText(false);
1654 mTextPaint.setTextSkewX(0);
1660 * Subclasses override this to specify that they have a KeyListener
1661 * by default even if not specifically called for in the XML options.
1663 protected boolean getDefaultEditable() {
1668 * Subclasses override this to specify a default movement method.
1670 protected MovementMethod getDefaultMovementMethod() {
1675 * Return the text the TextView is displaying. If setText() was called with
1676 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1677 * the return value from this method to Spannable or Editable, respectively.
1679 * Note: The content of the return value should not be modified. If you want
1680 * a modifiable one, you should make your own copy first.
1682 * @attr ref android.R.styleable#TextView_text
1684 @ViewDebug.CapturedViewProperty
1685 public CharSequence getText() {
1690 * Returns the length, in characters, of the text managed by this TextView
1692 public int length() {
1693 return mText.length();
1697 * Return the text the TextView is displaying as an Editable object. If
1698 * the text is not editable, null is returned.
1702 public Editable getEditableText() {
1703 return (mText instanceof Editable) ? (Editable)mText : null;
1707 * @return the height of one standard line in pixels. Note that markup
1708 * within the text can cause individual lines to be taller or shorter
1709 * than this height, and the layout may contain additional first-
1710 * or last-line padding.
1712 public int getLineHeight() {
1713 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1717 * @return the Layout that is currently being used to display the text.
1718 * This can be null if the text or width has recently changes.
1720 public final Layout getLayout() {
1725 * @return the Layout that is currently being used to display the hint text.
1728 final Layout getHintLayout() {
1733 * Retrieve the {@link android.content.UndoManager} that is currently associated
1734 * with this TextView. By default there is no associated UndoManager, so null
1735 * is returned. One can be associated with the TextView through
1736 * {@link #setUndoManager(android.content.UndoManager, String)}
1740 public final UndoManager getUndoManager() {
1741 // TODO: Consider supporting a global undo manager.
1742 throw new UnsupportedOperationException("not implemented");
1750 public final Editor getEditorForTesting() {
1755 * Associate an {@link android.content.UndoManager} with this TextView. Once
1756 * done, all edit operations on the TextView will result in appropriate
1757 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1760 * @param undoManager The {@link android.content.UndoManager} to associate with
1761 * this TextView, or null to clear any existing association.
1762 * @param tag String tag identifying this particular TextView owner in the
1763 * UndoManager. This is used to keep the correct association with the
1764 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1768 public final void setUndoManager(UndoManager undoManager, String tag) {
1769 // TODO: Consider supporting a global undo manager. An implementation will need to:
1770 // * createEditorIfNeeded()
1771 // * Promote to BufferType.EDITABLE if needed.
1772 // * Update the UndoManager and UndoOwner.
1773 // Likewise it will need to be able to restore the default UndoManager.
1774 throw new UnsupportedOperationException("not implemented");
1778 * @return the current key listener for this TextView.
1779 * This will frequently be null for non-EditText TextViews.
1781 * @attr ref android.R.styleable#TextView_numeric
1782 * @attr ref android.R.styleable#TextView_digits
1783 * @attr ref android.R.styleable#TextView_phoneNumber
1784 * @attr ref android.R.styleable#TextView_inputMethod
1785 * @attr ref android.R.styleable#TextView_capitalize
1786 * @attr ref android.R.styleable#TextView_autoText
1788 public final KeyListener getKeyListener() {
1789 return mEditor == null ? null : mEditor.mKeyListener;
1793 * Sets the key listener to be used with this TextView. This can be null
1794 * to disallow user input. Note that this method has significant and
1795 * subtle interactions with soft keyboards and other input method:
1796 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1797 * for important details. Calling this method will replace the current
1798 * content type of the text view with the content type returned by the
1801 * Be warned that if you want a TextView with a key listener or movement
1802 * method not to be focusable, or if you want a TextView without a
1803 * key listener or movement method to be focusable, you must call
1804 * {@link #setFocusable} again after calling this to get the focusability
1805 * back the way you want it.
1807 * @attr ref android.R.styleable#TextView_numeric
1808 * @attr ref android.R.styleable#TextView_digits
1809 * @attr ref android.R.styleable#TextView_phoneNumber
1810 * @attr ref android.R.styleable#TextView_inputMethod
1811 * @attr ref android.R.styleable#TextView_capitalize
1812 * @attr ref android.R.styleable#TextView_autoText
1814 public void setKeyListener(KeyListener input) {
1815 setKeyListenerOnly(input);
1816 fixFocusableAndClickableSettings();
1818 if (input != null) {
1819 createEditorIfNeeded();
1821 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1822 } catch (IncompatibleClassChangeError e) {
1823 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1825 // Change inputType, without affecting transformation.
1826 // No need to applySingleLine since mSingleLine is unchanged.
1827 setInputTypeSingleLine(mSingleLine);
1829 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1832 InputMethodManager imm = InputMethodManager.peekInstance();
1833 if (imm != null) imm.restartInput(this);
1836 private void setKeyListenerOnly(KeyListener input) {
1837 if (mEditor == null && input == null) return; // null is the default value
1839 createEditorIfNeeded();
1840 if (mEditor.mKeyListener != input) {
1841 mEditor.mKeyListener = input;
1842 if (input != null && !(mText instanceof Editable)) {
1846 setFilters((Editable) mText, mFilters);
1851 * @return the movement method being used for this TextView.
1852 * This will frequently be null for non-EditText TextViews.
1854 public final MovementMethod getMovementMethod() {
1859 * Sets the movement method (arrow key handler) to be used for
1860 * this TextView. This can be null to disallow using the arrow keys
1861 * to move the cursor or scroll the view.
1863 * Be warned that if you want a TextView with a key listener or movement
1864 * method not to be focusable, or if you want a TextView without a
1865 * key listener or movement method to be focusable, you must call
1866 * {@link #setFocusable} again after calling this to get the focusability
1867 * back the way you want it.
1869 public final void setMovementMethod(MovementMethod movement) {
1870 if (mMovement != movement) {
1871 mMovement = movement;
1873 if (movement != null && !(mText instanceof Spannable)) {
1877 fixFocusableAndClickableSettings();
1879 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1881 if (mEditor != null) mEditor.prepareCursorControllers();
1885 private void fixFocusableAndClickableSettings() {
1886 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1889 setLongClickable(true);
1891 setFocusable(false);
1892 setClickable(false);
1893 setLongClickable(false);
1898 * @return the current transformation method for this TextView.
1899 * This will frequently be null except for single-line and password
1902 * @attr ref android.R.styleable#TextView_password
1903 * @attr ref android.R.styleable#TextView_singleLine
1905 public final TransformationMethod getTransformationMethod() {
1906 return mTransformation;
1910 * Sets the transformation that is applied to the text that this
1911 * TextView is displaying.
1913 * @attr ref android.R.styleable#TextView_password
1914 * @attr ref android.R.styleable#TextView_singleLine
1916 public final void setTransformationMethod(TransformationMethod method) {
1917 if (method == mTransformation) {
1918 // Avoid the setText() below if the transformation is
1922 if (mTransformation != null) {
1923 if (mText instanceof Spannable) {
1924 ((Spannable) mText).removeSpan(mTransformation);
1928 mTransformation = method;
1930 if (method instanceof TransformationMethod2) {
1931 TransformationMethod2 method2 = (TransformationMethod2) method;
1932 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1933 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1935 mAllowTransformationLengthChange = false;
1940 if (hasPasswordTransformationMethod()) {
1941 notifyViewAccessibilityStateChangedIfNeeded(
1942 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1947 * Returns the top padding of the view, plus space for the top
1950 public int getCompoundPaddingTop() {
1951 final Drawables dr = mDrawables;
1952 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
1955 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1960 * Returns the bottom padding of the view, plus space for the bottom
1963 public int getCompoundPaddingBottom() {
1964 final Drawables dr = mDrawables;
1965 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
1966 return mPaddingBottom;
1968 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1973 * Returns the left padding of the view, plus space for the left
1976 public int getCompoundPaddingLeft() {
1977 final Drawables dr = mDrawables;
1978 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
1979 return mPaddingLeft;
1981 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1986 * Returns the right padding of the view, plus space for the right
1989 public int getCompoundPaddingRight() {
1990 final Drawables dr = mDrawables;
1991 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
1992 return mPaddingRight;
1994 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1999 * Returns the start padding of the view, plus space for the start
2002 public int getCompoundPaddingStart() {
2004 switch(getLayoutDirection()) {
2006 case LAYOUT_DIRECTION_LTR:
2007 return getCompoundPaddingLeft();
2008 case LAYOUT_DIRECTION_RTL:
2009 return getCompoundPaddingRight();
2014 * Returns the end padding of the view, plus space for the end
2017 public int getCompoundPaddingEnd() {
2019 switch(getLayoutDirection()) {
2021 case LAYOUT_DIRECTION_LTR:
2022 return getCompoundPaddingRight();
2023 case LAYOUT_DIRECTION_RTL:
2024 return getCompoundPaddingLeft();
2029 * Returns the extended top padding of the view, including both the
2030 * top Drawable if any and any extra space to keep more than maxLines
2031 * of text from showing. It is only valid to call this after measuring.
2033 public int getExtendedPaddingTop() {
2034 if (mMaxMode != LINES) {
2035 return getCompoundPaddingTop();
2038 if (mLayout == null) {
2042 if (mLayout.getLineCount() <= mMaximum) {
2043 return getCompoundPaddingTop();
2046 int top = getCompoundPaddingTop();
2047 int bottom = getCompoundPaddingBottom();
2048 int viewht = getHeight() - top - bottom;
2049 int layoutht = mLayout.getLineTop(mMaximum);
2051 if (layoutht >= viewht) {
2055 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2056 if (gravity == Gravity.TOP) {
2058 } else if (gravity == Gravity.BOTTOM) {
2059 return top + viewht - layoutht;
2060 } else { // (gravity == Gravity.CENTER_VERTICAL)
2061 return top + (viewht - layoutht) / 2;
2066 * Returns the extended bottom padding of the view, including both the
2067 * bottom Drawable if any and any extra space to keep more than maxLines
2068 * of text from showing. It is only valid to call this after measuring.
2070 public int getExtendedPaddingBottom() {
2071 if (mMaxMode != LINES) {
2072 return getCompoundPaddingBottom();
2075 if (mLayout == null) {
2079 if (mLayout.getLineCount() <= mMaximum) {
2080 return getCompoundPaddingBottom();
2083 int top = getCompoundPaddingTop();
2084 int bottom = getCompoundPaddingBottom();
2085 int viewht = getHeight() - top - bottom;
2086 int layoutht = mLayout.getLineTop(mMaximum);
2088 if (layoutht >= viewht) {
2092 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2093 if (gravity == Gravity.TOP) {
2094 return bottom + viewht - layoutht;
2095 } else if (gravity == Gravity.BOTTOM) {
2097 } else { // (gravity == Gravity.CENTER_VERTICAL)
2098 return bottom + (viewht - layoutht) / 2;
2103 * Returns the total left padding of the view, including the left
2106 public int getTotalPaddingLeft() {
2107 return getCompoundPaddingLeft();
2111 * Returns the total right padding of the view, including the right
2114 public int getTotalPaddingRight() {
2115 return getCompoundPaddingRight();
2119 * Returns the total start padding of the view, including the start
2122 public int getTotalPaddingStart() {
2123 return getCompoundPaddingStart();
2127 * Returns the total end padding of the view, including the end
2130 public int getTotalPaddingEnd() {
2131 return getCompoundPaddingEnd();
2135 * Returns the total top padding of the view, including the top
2136 * Drawable if any, the extra space to keep more than maxLines
2137 * from showing, and the vertical offset for gravity, if any.
2139 public int getTotalPaddingTop() {
2140 return getExtendedPaddingTop() + getVerticalOffset(true);
2144 * Returns the total bottom padding of the view, including the bottom
2145 * Drawable if any, the extra space to keep more than maxLines
2146 * from showing, and the vertical offset for gravity, if any.
2148 public int getTotalPaddingBottom() {
2149 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2153 * Sets the Drawables (if any) to appear to the left of, above, to the
2154 * right of, and below the text. Use {@code null} if you do not want a
2155 * Drawable there. The Drawables must already have had
2156 * {@link Drawable#setBounds} called.
2158 * Calling this method will overwrite any Drawables previously set using
2159 * {@link #setCompoundDrawablesRelative} or related methods.
2161 * @attr ref android.R.styleable#TextView_drawableLeft
2162 * @attr ref android.R.styleable#TextView_drawableTop
2163 * @attr ref android.R.styleable#TextView_drawableRight
2164 * @attr ref android.R.styleable#TextView_drawableBottom
2166 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2167 @Nullable Drawable right, @Nullable Drawable bottom) {
2168 Drawables dr = mDrawables;
2170 // We're switching to absolute, discard relative.
2172 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2173 dr.mDrawableStart = null;
2174 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2175 dr.mDrawableEnd = null;
2176 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2177 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2180 final boolean drawables = left != null || top != null || right != null || bottom != null;
2182 // Clearing drawables... can we free the data structure?
2184 if (!dr.hasMetadata()) {
2187 // We need to retain the last set padding, so just clear
2188 // out all of the fields in the existing structure.
2189 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2190 if (dr.mShowing[i] != null) {
2191 dr.mShowing[i].setCallback(null);
2193 dr.mShowing[i] = null;
2195 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2196 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2197 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2198 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2203 mDrawables = dr = new Drawables(getContext());
2206 mDrawables.mOverride = false;
2208 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2209 dr.mShowing[Drawables.LEFT].setCallback(null);
2211 dr.mShowing[Drawables.LEFT] = left;
2213 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2214 dr.mShowing[Drawables.TOP].setCallback(null);
2216 dr.mShowing[Drawables.TOP] = top;
2218 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2219 dr.mShowing[Drawables.RIGHT].setCallback(null);
2221 dr.mShowing[Drawables.RIGHT] = right;
2223 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2224 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2226 dr.mShowing[Drawables.BOTTOM] = bottom;
2228 final Rect compoundRect = dr.mCompoundRect;
2231 state = getDrawableState();
2234 left.setState(state);
2235 left.copyBounds(compoundRect);
2236 left.setCallback(this);
2237 dr.mDrawableSizeLeft = compoundRect.width();
2238 dr.mDrawableHeightLeft = compoundRect.height();
2240 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2243 if (right != null) {
2244 right.setState(state);
2245 right.copyBounds(compoundRect);
2246 right.setCallback(this);
2247 dr.mDrawableSizeRight = compoundRect.width();
2248 dr.mDrawableHeightRight = compoundRect.height();
2250 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2254 top.setState(state);
2255 top.copyBounds(compoundRect);
2256 top.setCallback(this);
2257 dr.mDrawableSizeTop = compoundRect.height();
2258 dr.mDrawableWidthTop = compoundRect.width();
2260 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2263 if (bottom != null) {
2264 bottom.setState(state);
2265 bottom.copyBounds(compoundRect);
2266 bottom.setCallback(this);
2267 dr.mDrawableSizeBottom = compoundRect.height();
2268 dr.mDrawableWidthBottom = compoundRect.width();
2270 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2274 // Save initial left/right drawables
2276 dr.mDrawableLeftInitial = left;
2277 dr.mDrawableRightInitial = right;
2280 resetResolvedDrawables();
2282 applyCompoundDrawableTint();
2288 * Sets the Drawables (if any) to appear to the left of, above, to the
2289 * right of, and below the text. Use 0 if you do not want a Drawable there.
2290 * The Drawables' bounds will be set to their intrinsic bounds.
2292 * Calling this method will overwrite any Drawables previously set using
2293 * {@link #setCompoundDrawablesRelative} or related methods.
2295 * @param left Resource identifier of the left Drawable.
2296 * @param top Resource identifier of the top Drawable.
2297 * @param right Resource identifier of the right Drawable.
2298 * @param bottom Resource identifier of the bottom Drawable.
2300 * @attr ref android.R.styleable#TextView_drawableLeft
2301 * @attr ref android.R.styleable#TextView_drawableTop
2302 * @attr ref android.R.styleable#TextView_drawableRight
2303 * @attr ref android.R.styleable#TextView_drawableBottom
2305 @android.view.RemotableViewMethod
2306 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2307 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2308 final Context context = getContext();
2309 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2310 top != 0 ? context.getDrawable(top) : null,
2311 right != 0 ? context.getDrawable(right) : null,
2312 bottom != 0 ? context.getDrawable(bottom) : null);
2316 * Sets the Drawables (if any) to appear to the left of, above, to the
2317 * right of, and below the text. Use {@code null} if you do not want a
2318 * Drawable there. The Drawables' bounds will be set to their intrinsic
2321 * Calling this method will overwrite any Drawables previously set using
2322 * {@link #setCompoundDrawablesRelative} or related methods.
2324 * @attr ref android.R.styleable#TextView_drawableLeft
2325 * @attr ref android.R.styleable#TextView_drawableTop
2326 * @attr ref android.R.styleable#TextView_drawableRight
2327 * @attr ref android.R.styleable#TextView_drawableBottom
2329 @android.view.RemotableViewMethod
2330 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2331 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2334 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2336 if (right != null) {
2337 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2340 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2342 if (bottom != null) {
2343 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2345 setCompoundDrawables(left, top, right, bottom);
2349 * Sets the Drawables (if any) to appear to the start of, above, to the end
2350 * of, and below the text. Use {@code null} if you do not want a Drawable
2351 * there. The Drawables must already have had {@link Drawable#setBounds}
2354 * Calling this method will overwrite any Drawables previously set using
2355 * {@link #setCompoundDrawables} or related methods.
2357 * @attr ref android.R.styleable#TextView_drawableStart
2358 * @attr ref android.R.styleable#TextView_drawableTop
2359 * @attr ref android.R.styleable#TextView_drawableEnd
2360 * @attr ref android.R.styleable#TextView_drawableBottom
2362 @android.view.RemotableViewMethod
2363 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2364 @Nullable Drawable end, @Nullable Drawable bottom) {
2365 Drawables dr = mDrawables;
2367 // We're switching to relative, discard absolute.
2369 if (dr.mShowing[Drawables.LEFT] != null) {
2370 dr.mShowing[Drawables.LEFT].setCallback(null);
2372 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2373 if (dr.mShowing[Drawables.RIGHT] != null) {
2374 dr.mShowing[Drawables.RIGHT].setCallback(null);
2376 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2377 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2378 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2381 final boolean drawables = start != null || top != null
2382 || end != null || bottom != null;
2385 // Clearing drawables... can we free the data structure?
2387 if (!dr.hasMetadata()) {
2390 // We need to retain the last set padding, so just clear
2391 // out all of the fields in the existing structure.
2392 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2393 dr.mDrawableStart = null;
2394 if (dr.mShowing[Drawables.TOP] != null) {
2395 dr.mShowing[Drawables.TOP].setCallback(null);
2397 dr.mShowing[Drawables.TOP] = null;
2398 if (dr.mDrawableEnd != null) {
2399 dr.mDrawableEnd.setCallback(null);
2401 dr.mDrawableEnd = null;
2402 if (dr.mShowing[Drawables.BOTTOM] != null) {
2403 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2405 dr.mShowing[Drawables.BOTTOM] = null;
2406 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2407 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2408 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2409 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2414 mDrawables = dr = new Drawables(getContext());
2417 mDrawables.mOverride = true;
2419 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2420 dr.mDrawableStart.setCallback(null);
2422 dr.mDrawableStart = start;
2424 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2425 dr.mShowing[Drawables.TOP].setCallback(null);
2427 dr.mShowing[Drawables.TOP] = top;
2429 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2430 dr.mDrawableEnd.setCallback(null);
2432 dr.mDrawableEnd = end;
2434 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2435 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2437 dr.mShowing[Drawables.BOTTOM] = bottom;
2439 final Rect compoundRect = dr.mCompoundRect;
2442 state = getDrawableState();
2444 if (start != null) {
2445 start.setState(state);
2446 start.copyBounds(compoundRect);
2447 start.setCallback(this);
2448 dr.mDrawableSizeStart = compoundRect.width();
2449 dr.mDrawableHeightStart = compoundRect.height();
2451 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2455 end.setState(state);
2456 end.copyBounds(compoundRect);
2457 end.setCallback(this);
2458 dr.mDrawableSizeEnd = compoundRect.width();
2459 dr.mDrawableHeightEnd = compoundRect.height();
2461 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2465 top.setState(state);
2466 top.copyBounds(compoundRect);
2467 top.setCallback(this);
2468 dr.mDrawableSizeTop = compoundRect.height();
2469 dr.mDrawableWidthTop = compoundRect.width();
2471 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2474 if (bottom != null) {
2475 bottom.setState(state);
2476 bottom.copyBounds(compoundRect);
2477 bottom.setCallback(this);
2478 dr.mDrawableSizeBottom = compoundRect.height();
2479 dr.mDrawableWidthBottom = compoundRect.width();
2481 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2485 resetResolvedDrawables();
2492 * Sets the Drawables (if any) to appear to the start of, above, to the end
2493 * of, and below the text. Use 0 if you do not want a Drawable there. The
2494 * Drawables' bounds will be set to their intrinsic bounds.
2496 * Calling this method will overwrite any Drawables previously set using
2497 * {@link #setCompoundDrawables} or related methods.
2499 * @param start Resource identifier of the start Drawable.
2500 * @param top Resource identifier of the top Drawable.
2501 * @param end Resource identifier of the end Drawable.
2502 * @param bottom Resource identifier of the bottom Drawable.
2504 * @attr ref android.R.styleable#TextView_drawableStart
2505 * @attr ref android.R.styleable#TextView_drawableTop
2506 * @attr ref android.R.styleable#TextView_drawableEnd
2507 * @attr ref android.R.styleable#TextView_drawableBottom
2509 @android.view.RemotableViewMethod
2510 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2511 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
2512 final Context context = getContext();
2513 setCompoundDrawablesRelativeWithIntrinsicBounds(
2514 start != 0 ? context.getDrawable(start) : null,
2515 top != 0 ? context.getDrawable(top) : null,
2516 end != 0 ? context.getDrawable(end) : null,
2517 bottom != 0 ? context.getDrawable(bottom) : null);
2521 * Sets the Drawables (if any) to appear to the start of, above, to the end
2522 * of, and below the text. Use {@code null} if you do not want a Drawable
2523 * there. The Drawables' bounds will be set to their intrinsic bounds.
2525 * Calling this method will overwrite any Drawables previously set using
2526 * {@link #setCompoundDrawables} or related methods.
2528 * @attr ref android.R.styleable#TextView_drawableStart
2529 * @attr ref android.R.styleable#TextView_drawableTop
2530 * @attr ref android.R.styleable#TextView_drawableEnd
2531 * @attr ref android.R.styleable#TextView_drawableBottom
2533 @android.view.RemotableViewMethod
2534 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2535 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2537 if (start != null) {
2538 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2541 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2544 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2546 if (bottom != null) {
2547 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2549 setCompoundDrawablesRelative(start, top, end, bottom);
2553 * Returns drawables for the left, top, right, and bottom borders.
2555 * @attr ref android.R.styleable#TextView_drawableLeft
2556 * @attr ref android.R.styleable#TextView_drawableTop
2557 * @attr ref android.R.styleable#TextView_drawableRight
2558 * @attr ref android.R.styleable#TextView_drawableBottom
2561 public Drawable[] getCompoundDrawables() {
2562 final Drawables dr = mDrawables;
2564 return dr.mShowing.clone();
2566 return new Drawable[] { null, null, null, null };
2571 * Returns drawables for the start, top, end, and bottom borders.
2573 * @attr ref android.R.styleable#TextView_drawableStart
2574 * @attr ref android.R.styleable#TextView_drawableTop
2575 * @attr ref android.R.styleable#TextView_drawableEnd
2576 * @attr ref android.R.styleable#TextView_drawableBottom
2579 public Drawable[] getCompoundDrawablesRelative() {
2580 final Drawables dr = mDrawables;
2582 return new Drawable[] {
2583 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2584 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
2587 return new Drawable[] { null, null, null, null };
2592 * Sets the size of the padding between the compound drawables and
2595 * @attr ref android.R.styleable#TextView_drawablePadding
2597 @android.view.RemotableViewMethod
2598 public void setCompoundDrawablePadding(int pad) {
2599 Drawables dr = mDrawables;
2602 dr.mDrawablePadding = pad;
2606 mDrawables = dr = new Drawables(getContext());
2608 dr.mDrawablePadding = pad;
2616 * Returns the padding between the compound drawables and the text.
2618 * @attr ref android.R.styleable#TextView_drawablePadding
2620 public int getCompoundDrawablePadding() {
2621 final Drawables dr = mDrawables;
2622 return dr != null ? dr.mDrawablePadding : 0;
2626 * Applies a tint to the compound drawables. Does not modify the
2627 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2629 * Subsequent calls to
2630 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2631 * and related methods will automatically mutate the drawables and apply
2632 * the specified tint and tint mode using
2633 * {@link Drawable#setTintList(ColorStateList)}.
2635 * @param tint the tint to apply, may be {@code null} to clear tint
2637 * @attr ref android.R.styleable#TextView_drawableTint
2638 * @see #getCompoundDrawableTintList()
2639 * @see Drawable#setTintList(ColorStateList)
2641 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2642 if (mDrawables == null) {
2643 mDrawables = new Drawables(getContext());
2645 mDrawables.mTintList = tint;
2646 mDrawables.mHasTint = true;
2648 applyCompoundDrawableTint();
2652 * @return the tint applied to the compound drawables
2653 * @attr ref android.R.styleable#TextView_drawableTint
2654 * @see #setCompoundDrawableTintList(ColorStateList)
2656 public ColorStateList getCompoundDrawableTintList() {
2657 return mDrawables != null ? mDrawables.mTintList : null;
2661 * Specifies the blending mode used to apply the tint specified by
2662 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2663 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2665 * @param tintMode the blending mode used to apply the tint, may be
2666 * {@code null} to clear tint
2667 * @attr ref android.R.styleable#TextView_drawableTintMode
2668 * @see #setCompoundDrawableTintList(ColorStateList)
2669 * @see Drawable#setTintMode(PorterDuff.Mode)
2671 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2672 if (mDrawables == null) {
2673 mDrawables = new Drawables(getContext());
2675 mDrawables.mTintMode = tintMode;
2676 mDrawables.mHasTintMode = true;
2678 applyCompoundDrawableTint();
2682 * Returns the blending mode used to apply the tint to the compound
2683 * drawables, if specified.
2685 * @return the blending mode used to apply the tint to the compound
2687 * @attr ref android.R.styleable#TextView_drawableTintMode
2688 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2690 public PorterDuff.Mode getCompoundDrawableTintMode() {
2691 return mDrawables != null ? mDrawables.mTintMode : null;
2694 private void applyCompoundDrawableTint() {
2695 if (mDrawables == null) {
2699 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2700 final ColorStateList tintList = mDrawables.mTintList;
2701 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2702 final boolean hasTint = mDrawables.mHasTint;
2703 final boolean hasTintMode = mDrawables.mHasTintMode;
2704 final int[] state = getDrawableState();
2706 for (Drawable dr : mDrawables.mShowing) {
2711 if (dr == mDrawables.mDrawableError) {
2712 // From a developer's perspective, the error drawable isn't
2713 // a compound drawable. Don't apply the generic compound
2714 // drawable tint to it.
2721 dr.setTintList(tintList);
2725 dr.setTintMode(tintMode);
2728 // The drawable (or one of its children) may not have been
2729 // stateful before applying the tint, so let's try again.
2730 if (dr.isStateful()) {
2738 public void setPadding(int left, int top, int right, int bottom) {
2739 if (left != mPaddingLeft ||
2740 right != mPaddingRight ||
2741 top != mPaddingTop ||
2742 bottom != mPaddingBottom) {
2746 // the super call will requestLayout()
2747 super.setPadding(left, top, right, bottom);
2752 public void setPaddingRelative(int start, int top, int end, int bottom) {
2753 if (start != getPaddingStart() ||
2754 end != getPaddingEnd() ||
2755 top != mPaddingTop ||
2756 bottom != mPaddingBottom) {
2760 // the super call will requestLayout()
2761 super.setPaddingRelative(start, top, end, bottom);
2766 * Gets the autolink mask of the text. See {@link
2767 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2770 * @attr ref android.R.styleable#TextView_autoLink
2772 public final int getAutoLinkMask() {
2773 return mAutoLinkMask;
2777 * Sets the text appearance from the specified style resource.
2779 * Use a framework-defined {@code TextAppearance} style like
2780 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2781 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2782 * set of attributes that can be used in a custom style.
2784 * @param resId the resource identifier of the style to apply
2785 * @attr ref android.R.styleable#TextView_textAppearance
2787 @SuppressWarnings("deprecation")
2788 public void setTextAppearance(@StyleRes int resId) {
2789 setTextAppearance(mContext, resId);
2793 * Sets the text color, size, style, hint color, and highlight color
2794 * from the specified TextAppearance resource.
2796 * @deprecated Use {@link #setTextAppearance(int)} instead.
2799 public void setTextAppearance(Context context, @StyleRes int resId) {
2800 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
2802 final int textColorHighlight = ta.getColor(
2803 R.styleable.TextAppearance_textColorHighlight, 0);
2804 if (textColorHighlight != 0) {
2805 setHighlightColor(textColorHighlight);
2808 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2809 if (textColor != null) {
2810 setTextColor(textColor);
2813 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2814 if (textSize != 0) {
2815 setRawTextSize(textSize);
2818 final ColorStateList textColorHint = ta.getColorStateList(
2819 R.styleable.TextAppearance_textColorHint);
2820 if (textColorHint != null) {
2821 setHintTextColor(textColorHint);
2824 final ColorStateList textColorLink = ta.getColorStateList(
2825 R.styleable.TextAppearance_textColorLink);
2826 if (textColorLink != null) {
2827 setLinkTextColor(textColorLink);
2830 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2831 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2832 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2833 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
2835 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2836 if (shadowColor != 0) {
2837 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2838 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2839 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2840 setShadowLayer(r, dx, dy, shadowColor);
2843 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
2844 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2847 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2848 setElegantTextHeight(ta.getBoolean(
2849 R.styleable.TextAppearance_elegantTextHeight, false));
2852 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2853 setLetterSpacing(ta.getFloat(
2854 R.styleable.TextAppearance_letterSpacing, 0));
2857 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2858 setFontFeatureSettings(ta.getString(
2859 R.styleable.TextAppearance_fontFeatureSettings));
2866 * Get the default primary {@link Locale} of the text in this TextView. This will always be
2867 * the first member of {@link #getTextLocales()}.
2868 * @return the default primary {@link Locale} of the text in this TextView.
2871 public Locale getTextLocale() {
2872 return mTextPaint.getTextLocale();
2876 * Get the default {@link LocaleList} of the text in this TextView.
2877 * @return the default {@link LocaleList} of the text in this TextView.
2879 @NonNull @Size(min=1)
2880 public LocaleList getTextLocales() {
2881 return mTextPaint.getTextLocales();
2885 * Set the default {@link LocaleList} of the text in this TextView to a one-member list
2886 * containing just the given value.
2888 * @param locale the {@link Locale} for drawing text, must not be null.
2890 * @see #setTextLocales
2892 public void setTextLocale(@NonNull Locale locale) {
2893 mLocalesChanged = true;
2894 mTextPaint.setTextLocale(locale);
2898 * Set the default {@link LocaleList} of the text in this TextView to the given value.
2900 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
2901 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
2902 * other aspects of text display, including line breaking.
2904 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
2906 * @see Paint#setTextLocales
2908 public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
2909 mLocalesChanged = true;
2910 mTextPaint.setTextLocales(locales);
2914 protected void onConfigurationChanged(Configuration newConfig) {
2915 super.onConfigurationChanged(newConfig);
2916 if (!mLocalesChanged) {
2917 mTextPaint.setTextLocales(LocaleList.getDefault());
2922 * @return the size (in pixels) of the default text size in this TextView.
2924 @ViewDebug.ExportedProperty(category = "text")
2925 public float getTextSize() {
2926 return mTextPaint.getTextSize();
2930 * @return the size (in scaled pixels) of thee default text size in this TextView.
2933 @ViewDebug.ExportedProperty(category = "text")
2934 public float getScaledTextSize() {
2935 return mTextPaint.getTextSize() / mTextPaint.density;
2939 @ViewDebug.ExportedProperty(category = "text", mapping = {
2940 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2941 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2942 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2943 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2945 public int getTypefaceStyle() {
2946 Typeface typeface = mTextPaint.getTypeface();
2947 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
2951 * Set the default text size to the given value, interpreted as "scaled
2952 * pixel" units. This size is adjusted based on the current density and
2953 * user font size preference.
2955 * @param size The scaled pixel size.
2957 * @attr ref android.R.styleable#TextView_textSize
2959 @android.view.RemotableViewMethod
2960 public void setTextSize(float size) {
2961 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2965 * Set the default text size to a given unit and value. See {@link
2966 * TypedValue} for the possible dimension units.
2968 * @param unit The desired dimension unit.
2969 * @param size The desired size in the given units.
2971 * @attr ref android.R.styleable#TextView_textSize
2973 public void setTextSize(int unit, float size) {
2974 Context c = getContext();
2978 r = Resources.getSystem();
2980 r = c.getResources();
2982 setRawTextSize(TypedValue.applyDimension(
2983 unit, size, r.getDisplayMetrics()));
2986 private void setRawTextSize(float size) {
2987 if (size != mTextPaint.getTextSize()) {
2988 mTextPaint.setTextSize(size);
2990 if (mLayout != null) {
2999 * @return the extent by which text is currently being stretched
3000 * horizontally. This will usually be 1.
3002 public float getTextScaleX() {
3003 return mTextPaint.getTextScaleX();
3007 * Sets the extent by which text should be stretched horizontally.
3009 * @attr ref android.R.styleable#TextView_textScaleX
3011 @android.view.RemotableViewMethod
3012 public void setTextScaleX(float size) {
3013 if (size != mTextPaint.getTextScaleX()) {
3014 mUserSetTextScaleX = true;
3015 mTextPaint.setTextScaleX(size);
3017 if (mLayout != null) {
3026 * Sets the typeface and style in which the text should be displayed.
3027 * Note that not all Typeface families actually have bold and italic
3028 * variants, so you may need to use
3029 * {@link #setTypeface(Typeface, int)} to get the appearance
3030 * that you actually want.
3032 * @see #getTypeface()
3034 * @attr ref android.R.styleable#TextView_fontFamily
3035 * @attr ref android.R.styleable#TextView_typeface
3036 * @attr ref android.R.styleable#TextView_textStyle
3038 public void setTypeface(Typeface tf) {
3039 if (mTextPaint.getTypeface() != tf) {
3040 mTextPaint.setTypeface(tf);
3042 if (mLayout != null) {
3051 * @return the current typeface and style in which the text is being
3054 * @see #setTypeface(Typeface)
3056 * @attr ref android.R.styleable#TextView_fontFamily
3057 * @attr ref android.R.styleable#TextView_typeface
3058 * @attr ref android.R.styleable#TextView_textStyle
3060 public Typeface getTypeface() {
3061 return mTextPaint.getTypeface();
3065 * Set the TextView's elegant height metrics flag. This setting selects font
3066 * variants that have not been compacted to fit Latin-based vertical
3067 * metrics, and also increases top and bottom bounds to provide more space.
3069 * @param elegant set the paint's elegant metrics flag.
3071 * @attr ref android.R.styleable#TextView_elegantTextHeight
3073 public void setElegantTextHeight(boolean elegant) {
3074 mTextPaint.setElegantTextHeight(elegant);
3078 * @return the extent by which text is currently being letter-spaced.
3079 * This will normally be 0.
3081 * @see #setLetterSpacing(float)
3082 * @see Paint#setLetterSpacing
3084 public float getLetterSpacing() {
3085 return mTextPaint.getLetterSpacing();
3089 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3090 * for slight expansion will be around 0.05. Negative values tighten text.
3092 * @see #getLetterSpacing()
3093 * @see Paint#getLetterSpacing
3095 * @attr ref android.R.styleable#TextView_letterSpacing
3097 @android.view.RemotableViewMethod
3098 public void setLetterSpacing(float letterSpacing) {
3099 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3100 mTextPaint.setLetterSpacing(letterSpacing);
3102 if (mLayout != null) {
3111 * @return the currently set font feature settings. Default is null.
3113 * @see #setFontFeatureSettings(String)
3114 * @see Paint#setFontFeatureSettings
3117 public String getFontFeatureSettings() {
3118 return mTextPaint.getFontFeatureSettings();
3122 * Sets the break strategy for breaking paragraphs into lines. The default value for
3123 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3124 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3125 * text "dancing" when being edited.
3127 * @attr ref android.R.styleable#TextView_breakStrategy
3128 * @see #getBreakStrategy()
3130 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3131 mBreakStrategy = breakStrategy;
3132 if (mLayout != null) {
3140 * @return the currently set break strategy.
3142 * @attr ref android.R.styleable#TextView_breakStrategy
3143 * @see #setBreakStrategy(int)
3145 @Layout.BreakStrategy
3146 public int getBreakStrategy() {
3147 return mBreakStrategy;
3151 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3152 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3154 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3155 * @see #getHyphenationFrequency()
3157 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3158 mHyphenationFrequency = hyphenationFrequency;
3159 if (mLayout != null) {
3167 * @return the currently set hyphenation frequency.
3169 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3170 * @see #setHyphenationFrequency(int)
3172 @Layout.HyphenationFrequency
3173 public int getHyphenationFrequency() {
3174 return mHyphenationFrequency;
3178 * Sets font feature settings. The format is the same as the CSS
3179 * font-feature-settings attribute:
3180 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3182 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3183 * @see #getFontFeatureSettings()
3184 * @see Paint#getFontFeatureSettings
3186 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3188 @android.view.RemotableViewMethod
3189 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3190 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3191 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3193 if (mLayout != null) {
3203 * Sets the text color for all the states (normal, selected,
3204 * focused) to be this color.
3206 * @see #setTextColor(ColorStateList)
3207 * @see #getTextColors()
3209 * @attr ref android.R.styleable#TextView_textColor
3211 @android.view.RemotableViewMethod
3212 public void setTextColor(@ColorInt int color) {
3213 mTextColor = ColorStateList.valueOf(color);
3218 * Sets the text color.
3220 * @see #setTextColor(int)
3221 * @see #getTextColors()
3222 * @see #setHintTextColor(ColorStateList)
3223 * @see #setLinkTextColor(ColorStateList)
3225 * @attr ref android.R.styleable#TextView_textColor
3227 public void setTextColor(ColorStateList colors) {
3228 if (colors == null) {
3229 throw new NullPointerException();
3232 mTextColor = colors;
3237 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3239 * @see #setTextColor(ColorStateList)
3240 * @see #setTextColor(int)
3242 * @attr ref android.R.styleable#TextView_textColor
3244 public final ColorStateList getTextColors() {
3249 * <p>Return the current color selected for normal text.</p>
3251 * @return Returns the current text color.
3254 public final int getCurrentTextColor() {
3255 return mCurTextColor;
3259 * Sets the color used to display the selection highlight.
3261 * @attr ref android.R.styleable#TextView_textColorHighlight
3263 @android.view.RemotableViewMethod
3264 public void setHighlightColor(@ColorInt int color) {
3265 if (mHighlightColor != color) {
3266 mHighlightColor = color;
3272 * @return the color used to display the selection highlight
3274 * @see #setHighlightColor(int)
3276 * @attr ref android.R.styleable#TextView_textColorHighlight
3279 public int getHighlightColor() {
3280 return mHighlightColor;
3284 * Sets whether the soft input method will be made visible when this
3285 * TextView gets focused. The default is true.
3287 @android.view.RemotableViewMethod
3288 public final void setShowSoftInputOnFocus(boolean show) {
3289 createEditorIfNeeded();
3290 mEditor.mShowSoftInputOnFocus = show;
3294 * Returns whether the soft input method will be made visible when this
3295 * TextView gets focused. The default is true.
3297 public final boolean getShowSoftInputOnFocus() {
3298 // When there is no Editor, return default true value
3299 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3303 * Gives the text a shadow of the specified blur radius and color, the specified
3304 * distance from its drawn position.
3306 * The text shadow produced does not interact with the properties on view
3307 * that are responsible for real time shadows,
3308 * {@link View#getElevation() elevation} and
3309 * {@link View#getTranslationZ() translationZ}.
3311 * @see Paint#setShadowLayer(float, float, float, int)
3313 * @attr ref android.R.styleable#TextView_shadowColor
3314 * @attr ref android.R.styleable#TextView_shadowDx
3315 * @attr ref android.R.styleable#TextView_shadowDy
3316 * @attr ref android.R.styleable#TextView_shadowRadius
3318 public void setShadowLayer(float radius, float dx, float dy, int color) {
3319 mTextPaint.setShadowLayer(radius, dx, dy, color);
3321 mShadowRadius = radius;
3324 mShadowColor = color;
3326 // Will change text clip region
3327 if (mEditor != null) mEditor.invalidateTextDisplayList();
3332 * Gets the radius of the shadow layer.
3334 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3336 * @see #setShadowLayer(float, float, float, int)
3338 * @attr ref android.R.styleable#TextView_shadowRadius
3340 public float getShadowRadius() {
3341 return mShadowRadius;
3345 * @return the horizontal offset of the shadow layer
3347 * @see #setShadowLayer(float, float, float, int)
3349 * @attr ref android.R.styleable#TextView_shadowDx
3351 public float getShadowDx() {
3356 * @return the vertical offset of the shadow layer
3358 * @see #setShadowLayer(float, float, float, int)
3360 * @attr ref android.R.styleable#TextView_shadowDy
3362 public float getShadowDy() {
3367 * @return the color of the shadow layer
3369 * @see #setShadowLayer(float, float, float, int)
3371 * @attr ref android.R.styleable#TextView_shadowColor
3374 public int getShadowColor() {
3375 return mShadowColor;
3379 * @return the base paint used for the text. Please use this only to
3380 * consult the Paint's properties and not to change them.
3382 public TextPaint getPaint() {
3387 * Sets the autolink mask of the text. See {@link
3388 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3391 * @attr ref android.R.styleable#TextView_autoLink
3393 @android.view.RemotableViewMethod
3394 public final void setAutoLinkMask(int mask) {
3395 mAutoLinkMask = mask;
3399 * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
3400 * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
3401 * following is true:
3403 * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
3404 * {@link #setText} or {@link #append}.
3405 * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
3408 * <p>This function does not have an immediate effect, movement method will be set only after a
3409 * call to {@link #setText} or {@link #append}. The default is true.</p>
3411 * @attr ref android.R.styleable#TextView_linksClickable
3413 @android.view.RemotableViewMethod
3414 public final void setLinksClickable(boolean whether) {
3415 mLinksClickable = whether;
3419 * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
3420 * after {@link #setText} or {@link #append} is called.
3422 * See {@link #setLinksClickable} for details.
3424 * <p>The default is true.</p>
3426 * @see #setLinksClickable
3428 * @attr ref android.R.styleable#TextView_linksClickable
3430 public final boolean getLinksClickable() {
3431 return mLinksClickable;
3435 * Returns the list of URLSpans attached to the text
3436 * (by {@link Linkify} or otherwise) if any. You can call
3437 * {@link URLSpan#getURL} on them to find where they link to
3438 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3439 * to find the region of the text they are attached to.
3441 public URLSpan[] getUrls() {
3442 if (mText instanceof Spanned) {
3443 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3445 return new URLSpan[0];
3450 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3453 * @see #setHintTextColor(ColorStateList)
3454 * @see #getHintTextColors()
3455 * @see #setTextColor(int)
3457 * @attr ref android.R.styleable#TextView_textColorHint
3459 @android.view.RemotableViewMethod
3460 public final void setHintTextColor(@ColorInt int color) {
3461 mHintTextColor = ColorStateList.valueOf(color);
3466 * Sets the color of the hint text.
3468 * @see #getHintTextColors()
3469 * @see #setHintTextColor(int)
3470 * @see #setTextColor(ColorStateList)
3471 * @see #setLinkTextColor(ColorStateList)
3473 * @attr ref android.R.styleable#TextView_textColorHint
3475 public final void setHintTextColor(ColorStateList colors) {
3476 mHintTextColor = colors;
3481 * @return the color of the hint text, for the different states of this TextView.
3483 * @see #setHintTextColor(ColorStateList)
3484 * @see #setHintTextColor(int)
3485 * @see #setTextColor(ColorStateList)
3486 * @see #setLinkTextColor(ColorStateList)
3488 * @attr ref android.R.styleable#TextView_textColorHint
3490 public final ColorStateList getHintTextColors() {
3491 return mHintTextColor;
3495 * <p>Return the current color selected to paint the hint text.</p>
3497 * @return Returns the current hint text color.
3500 public final int getCurrentHintTextColor() {
3501 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3505 * Sets the color of links in the text.
3507 * @see #setLinkTextColor(ColorStateList)
3508 * @see #getLinkTextColors()
3510 * @attr ref android.R.styleable#TextView_textColorLink
3512 @android.view.RemotableViewMethod
3513 public final void setLinkTextColor(@ColorInt int color) {
3514 mLinkTextColor = ColorStateList.valueOf(color);
3519 * Sets the color of links in the text.
3521 * @see #setLinkTextColor(int)
3522 * @see #getLinkTextColors()
3523 * @see #setTextColor(ColorStateList)
3524 * @see #setHintTextColor(ColorStateList)
3526 * @attr ref android.R.styleable#TextView_textColorLink
3528 public final void setLinkTextColor(ColorStateList colors) {
3529 mLinkTextColor = colors;
3534 * @return the list of colors used to paint the links in the text, for the different states of
3537 * @see #setLinkTextColor(ColorStateList)
3538 * @see #setLinkTextColor(int)
3540 * @attr ref android.R.styleable#TextView_textColorLink
3542 public final ColorStateList getLinkTextColors() {
3543 return mLinkTextColor;
3547 * Sets the horizontal alignment of the text and the
3548 * vertical gravity that will be used when there is extra space
3549 * in the TextView beyond what is required for the text itself.
3551 * @see android.view.Gravity
3552 * @attr ref android.R.styleable#TextView_gravity
3554 public void setGravity(int gravity) {
3555 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3556 gravity |= Gravity.START;
3558 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3559 gravity |= Gravity.TOP;
3562 boolean newLayout = false;
3564 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3565 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3569 if (gravity != mGravity) {
3575 if (mLayout != null && newLayout) {
3576 // XXX this is heavy-handed because no actual content changes.
3577 int want = mLayout.getWidth();
3578 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3580 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3581 mRight - mLeft - getCompoundPaddingLeft() -
3582 getCompoundPaddingRight(), true);
3587 * Returns the horizontal and vertical alignment of this TextView.
3589 * @see android.view.Gravity
3590 * @attr ref android.R.styleable#TextView_gravity
3592 public int getGravity() {
3597 * @return the flags on the Paint being used to display the text.
3598 * @see Paint#getFlags
3600 public int getPaintFlags() {
3601 return mTextPaint.getFlags();
3605 * Sets flags on the Paint being used to display the text and
3606 * reflows the text if they are different from the old flags.
3607 * @see Paint#setFlags
3609 @android.view.RemotableViewMethod
3610 public void setPaintFlags(int flags) {
3611 if (mTextPaint.getFlags() != flags) {
3612 mTextPaint.setFlags(flags);
3614 if (mLayout != null) {
3623 * Sets whether the text should be allowed to be wider than the
3624 * View is. If false, it will be wrapped to the width of the View.
3626 * @attr ref android.R.styleable#TextView_scrollHorizontally
3628 public void setHorizontallyScrolling(boolean whether) {
3629 if (mHorizontallyScrolling != whether) {
3630 mHorizontallyScrolling = whether;
3632 if (mLayout != null) {
3641 * Returns whether the text is allowed to be wider than the View is.
3642 * If false, the text will be wrapped to the width of the View.
3644 * @attr ref android.R.styleable#TextView_scrollHorizontally
3647 public boolean getHorizontallyScrolling() {
3648 return mHorizontallyScrolling;
3652 * Makes the TextView at least this many lines tall.
3654 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3655 * set this value to 1.
3657 * @see #getMinLines()
3659 * @attr ref android.R.styleable#TextView_minLines
3661 @android.view.RemotableViewMethod
3662 public void setMinLines(int minlines) {
3663 mMinimum = minlines;
3671 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3672 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3674 * @see #setMinLines(int)
3676 * @attr ref android.R.styleable#TextView_minLines
3678 public int getMinLines() {
3679 return mMinMode == LINES ? mMinimum : -1;
3683 * Makes the TextView at least this many pixels tall.
3685 * Setting this value overrides any other (minimum) number of lines setting.
3687 * @attr ref android.R.styleable#TextView_minHeight
3689 @android.view.RemotableViewMethod
3690 public void setMinHeight(int minHeight) {
3691 mMinimum = minHeight;
3699 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3700 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3702 * @see #setMinHeight(int)
3704 * @attr ref android.R.styleable#TextView_minHeight
3706 public int getMinHeight() {
3707 return mMinMode == PIXELS ? mMinimum : -1;
3711 * Makes the TextView at most this many lines tall.
3713 * Setting this value overrides any other (maximum) height setting.
3715 * @attr ref android.R.styleable#TextView_maxLines
3717 @android.view.RemotableViewMethod
3718 public void setMaxLines(int maxlines) {
3719 mMaximum = maxlines;
3727 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3728 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3730 * @see #setMaxLines(int)
3732 * @attr ref android.R.styleable#TextView_maxLines
3734 public int getMaxLines() {
3735 return mMaxMode == LINES ? mMaximum : -1;
3739 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3740 * {@link #setMaxLines(int)} method.
3742 * Setting this value overrides any other (maximum) number of lines setting.
3744 * @attr ref android.R.styleable#TextView_maxHeight
3746 @android.view.RemotableViewMethod
3747 public void setMaxHeight(int maxHeight) {
3748 mMaximum = maxHeight;
3756 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3757 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3759 * @see #setMaxHeight(int)
3761 * @attr ref android.R.styleable#TextView_maxHeight
3763 public int getMaxHeight() {
3764 return mMaxMode == PIXELS ? mMaximum : -1;
3768 * Makes the TextView exactly this many lines tall.
3770 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3771 * height setting. A single line TextView will set this value to 1.
3773 * @attr ref android.R.styleable#TextView_lines
3775 @android.view.RemotableViewMethod
3776 public void setLines(int lines) {
3777 mMaximum = mMinimum = lines;
3778 mMaxMode = mMinMode = LINES;
3785 * Makes the TextView exactly this many pixels tall.
3786 * You could do the same thing by specifying this number in the
3789 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3792 * @attr ref android.R.styleable#TextView_height
3794 @android.view.RemotableViewMethod
3795 public void setHeight(int pixels) {
3796 mMaximum = mMinimum = pixels;
3797 mMaxMode = mMinMode = PIXELS;
3804 * Makes the TextView at least this many ems wide
3806 * @attr ref android.R.styleable#TextView_minEms
3808 @android.view.RemotableViewMethod
3809 public void setMinEms(int minems) {
3811 mMinWidthMode = EMS;
3818 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3819 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3821 * @see #setMinEms(int)
3824 * @attr ref android.R.styleable#TextView_minEms
3826 public int getMinEms() {
3827 return mMinWidthMode == EMS ? mMinWidth : -1;
3831 * Makes the TextView at least this many pixels wide
3833 * @attr ref android.R.styleable#TextView_minWidth
3835 @android.view.RemotableViewMethod
3836 public void setMinWidth(int minpixels) {
3837 mMinWidth = minpixels;
3838 mMinWidthMode = PIXELS;
3845 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3846 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3848 * @see #setMinWidth(int)
3849 * @see #setWidth(int)
3851 * @attr ref android.R.styleable#TextView_minWidth
3853 public int getMinWidth() {
3854 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3858 * Makes the TextView at most this many ems wide
3860 * @attr ref android.R.styleable#TextView_maxEms
3862 @android.view.RemotableViewMethod
3863 public void setMaxEms(int maxems) {
3865 mMaxWidthMode = EMS;
3872 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3873 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3875 * @see #setMaxEms(int)
3878 * @attr ref android.R.styleable#TextView_maxEms
3880 public int getMaxEms() {
3881 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3885 * Makes the TextView at most this many pixels wide
3887 * @attr ref android.R.styleable#TextView_maxWidth
3889 @android.view.RemotableViewMethod
3890 public void setMaxWidth(int maxpixels) {
3891 mMaxWidth = maxpixels;
3892 mMaxWidthMode = PIXELS;
3899 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3900 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3902 * @see #setMaxWidth(int)
3903 * @see #setWidth(int)
3905 * @attr ref android.R.styleable#TextView_maxWidth
3907 public int getMaxWidth() {
3908 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3912 * Makes the TextView exactly this many ems wide
3914 * @see #setMaxEms(int)
3915 * @see #setMinEms(int)
3919 * @attr ref android.R.styleable#TextView_ems
3921 @android.view.RemotableViewMethod
3922 public void setEms(int ems) {
3923 mMaxWidth = mMinWidth = ems;
3924 mMaxWidthMode = mMinWidthMode = EMS;
3931 * Makes the TextView exactly this many pixels wide.
3932 * You could do the same thing by specifying this number in the
3935 * @see #setMaxWidth(int)
3936 * @see #setMinWidth(int)
3937 * @see #getMinWidth()
3938 * @see #getMaxWidth()
3940 * @attr ref android.R.styleable#TextView_width
3942 @android.view.RemotableViewMethod
3943 public void setWidth(int pixels) {
3944 mMaxWidth = mMinWidth = pixels;
3945 mMaxWidthMode = mMinWidthMode = PIXELS;
3952 * Sets line spacing for this TextView. Each line will have its height
3953 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3955 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3956 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3958 public void setLineSpacing(float add, float mult) {
3959 if (mSpacingAdd != add || mSpacingMult != mult) {
3961 mSpacingMult = mult;
3963 if (mLayout != null) {
3972 * Gets the line spacing multiplier
3974 * @return the value by which each line's height is multiplied to get its actual height.
3976 * @see #setLineSpacing(float, float)
3977 * @see #getLineSpacingExtra()
3979 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3981 public float getLineSpacingMultiplier() {
3982 return mSpacingMult;
3986 * Gets the line spacing extra space
3988 * @return the extra space that is added to the height of each lines of this TextView.
3990 * @see #setLineSpacing(float, float)
3991 * @see #getLineSpacingMultiplier()
3993 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3995 public float getLineSpacingExtra() {
4000 * Convenience method: Append the specified text to the TextView's
4001 * display buffer, upgrading it to BufferType.EDITABLE if it was
4002 * not already editable.
4004 public final void append(CharSequence text) {
4005 append(text, 0, text.length());
4009 * Convenience method: Append the specified text slice to the TextView's
4010 * display buffer, upgrading it to BufferType.EDITABLE if it was
4011 * not already editable.
4013 public void append(CharSequence text, int start, int end) {
4014 if (!(mText instanceof Editable)) {
4015 setText(mText, BufferType.EDITABLE);
4018 ((Editable) mText).append(text, start, end);
4020 boolean hasClickableSpans = false;
4021 if (mAutoLinkMask != 0) {
4022 hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4023 } else if (mLinksClickable && text instanceof Spanned) {
4024 ClickableSpan[] clickableSpans =
4025 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4026 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4029 // Do not change the movement method for text that supports text selection as it
4030 // would prevent an arbitrary cursor displacement.
4031 if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
4032 setMovementMethod(LinkMovementMethod.getInstance());
4036 private void updateTextColors() {
4037 boolean inval = false;
4038 int color = mTextColor.getColorForState(getDrawableState(), 0);
4039 if (color != mCurTextColor) {
4040 mCurTextColor = color;
4043 if (mLinkTextColor != null) {
4044 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4045 if (color != mTextPaint.linkColor) {
4046 mTextPaint.linkColor = color;
4050 if (mHintTextColor != null) {
4051 color = mHintTextColor.getColorForState(getDrawableState(), 0);
4052 if (color != mCurHintTextColor) {
4053 mCurHintTextColor = color;
4054 if (mText.length() == 0) {
4060 // Text needs to be redrawn with the new color
4061 if (mEditor != null) mEditor.invalidateTextDisplayList();
4067 protected void drawableStateChanged() {
4068 super.drawableStateChanged();
4070 if (mTextColor != null && mTextColor.isStateful()
4071 || (mHintTextColor != null && mHintTextColor.isStateful())
4072 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4076 if (mDrawables != null) {
4077 final int[] state = getDrawableState();
4078 for (Drawable dr : mDrawables.mShowing) {
4079 if (dr != null && dr.isStateful() && dr.setState(state)) {
4080 invalidateDrawable(dr);
4087 public void drawableHotspotChanged(float x, float y) {
4088 super.drawableHotspotChanged(x, y);
4090 if (mDrawables != null) {
4091 for (Drawable dr : mDrawables.mShowing) {
4093 dr.setHotspot(x, y);
4100 public Parcelable onSaveInstanceState() {
4101 Parcelable superState = super.onSaveInstanceState();
4103 // Save state if we are forced to
4104 final boolean freezesText = getFreezesText();
4105 boolean hasSelection = false;
4109 if (mText != null) {
4110 start = getSelectionStart();
4111 end = getSelectionEnd();
4112 if (start >= 0 || end >= 0) {
4113 // Or save state if there is a selection
4114 hasSelection = true;
4118 if (freezesText || hasSelection) {
4119 SavedState ss = new SavedState(superState);
4122 if (mText instanceof Spanned) {
4123 final Spannable sp = new SpannableStringBuilder(mText);
4125 if (mEditor != null) {
4126 removeMisspelledSpans(sp);
4127 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4132 ss.text = mText.toString();
4137 // XXX Should also save the current scroll position!
4138 ss.selStart = start;
4142 if (isFocused() && start >= 0 && end >= 0) {
4143 ss.frozenWithFocus = true;
4146 ss.error = getError();
4148 if (mEditor != null) {
4149 ss.editorState = mEditor.saveInstanceState();
4157 void removeMisspelledSpans(Spannable spannable) {
4158 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4159 SuggestionSpan.class);
4160 for (int i = 0; i < suggestionSpans.length; i++) {
4161 int flags = suggestionSpans[i].getFlags();
4162 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4163 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4164 spannable.removeSpan(suggestionSpans[i]);
4170 public void onRestoreInstanceState(Parcelable state) {
4171 if (!(state instanceof SavedState)) {
4172 super.onRestoreInstanceState(state);
4176 SavedState ss = (SavedState)state;
4177 super.onRestoreInstanceState(ss.getSuperState());
4179 // XXX restore buffer type too, as well as lots of other stuff
4180 if (ss.text != null) {
4184 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4185 if (mText instanceof Spannable) {
4186 int len = mText.length();
4188 if (ss.selStart > len || ss.selEnd > len) {
4189 String restored = "";
4191 if (ss.text != null) {
4192 restored = "(restored) ";
4195 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4196 "/" + ss.selEnd + " out of range for " + restored +
4199 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4201 if (ss.frozenWithFocus) {
4202 createEditorIfNeeded();
4203 mEditor.mFrozenWithFocus = true;
4209 if (ss.error != null) {
4210 final CharSequence error = ss.error;
4211 // Display the error later, after the first layout pass
4212 post(new Runnable() {
4214 if (mEditor == null || !mEditor.mErrorWasChanged) {
4221 if (ss.editorState != null) {
4222 createEditorIfNeeded();
4223 mEditor.restoreInstanceState(ss.editorState);
4228 * Control whether this text view saves its entire text contents when
4229 * freezing to an icicle, in addition to dynamic state such as cursor
4230 * position. By default this is false, not saving the text. Set to true
4231 * if the text in the text view is not being saved somewhere else in
4232 * persistent storage (such as in a content provider) so that if the
4233 * view is later thawed the user will not lose their data. For
4234 * {@link android.widget.EditText} it is always enabled, regardless of
4235 * the value of the attribute.
4237 * @param freezesText Controls whether a frozen icicle should include the
4238 * entire text data: true to include it, false to not.
4240 * @attr ref android.R.styleable#TextView_freezesText
4242 @android.view.RemotableViewMethod
4243 public void setFreezesText(boolean freezesText) {
4244 mFreezesText = freezesText;
4248 * Return whether this text view is including its entire text contents
4249 * in frozen icicles. For {@link android.widget.EditText} it always returns true.
4251 * @return Returns true if text is included, false if it isn't.
4253 * @see #setFreezesText
4255 public boolean getFreezesText() {
4256 return mFreezesText;
4259 ///////////////////////////////////////////////////////////////////////////
4262 * Sets the Factory used to create new Editables.
4264 public final void setEditableFactory(Editable.Factory factory) {
4265 mEditableFactory = factory;
4270 * Sets the Factory used to create new Spannables.
4272 public final void setSpannableFactory(Spannable.Factory factory) {
4273 mSpannableFactory = factory;
4278 * Sets the string value of the TextView. TextView <em>does not</em> accept
4279 * HTML-like formatting, which you can do with text strings in XML resource files.
4280 * To style your strings, attach android.text.style.* objects to a
4281 * {@link android.text.SpannableString SpannableString}, or see the
4282 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4283 * Available Resource Types</a> documentation for an example of setting
4284 * formatted text in the XML resource file.
4286 * @attr ref android.R.styleable#TextView_text
4288 @android.view.RemotableViewMethod
4289 public final void setText(CharSequence text) {
4290 setText(text, mBufferType);
4294 * Like {@link #setText(CharSequence)},
4295 * except that the cursor position (if any) is retained in the new text.
4297 * @param text The new text to place in the text view.
4299 * @see #setText(CharSequence)
4301 @android.view.RemotableViewMethod
4302 public final void setTextKeepState(CharSequence text) {
4303 setTextKeepState(text, mBufferType);
4307 * Sets the text that this TextView is to display (see
4308 * {@link #setText(CharSequence)}) and also sets whether it is stored
4309 * in a styleable/spannable buffer and whether it is editable.
4311 * @attr ref android.R.styleable#TextView_text
4312 * @attr ref android.R.styleable#TextView_bufferType
4314 public void setText(CharSequence text, BufferType type) {
4315 setText(text, type, true, 0);
4317 if (mCharWrapper != null) {
4318 mCharWrapper.mChars = null;
4322 private void setText(CharSequence text, BufferType type,
4323 boolean notifyBefore, int oldlen) {
4328 // If suggestions are not enabled, remove the suggestion spans from the text
4329 if (!isSuggestionsEnabled()) {
4330 text = removeSuggestionSpans(text);
4333 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4335 if (text instanceof Spanned &&
4336 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4337 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4338 setHorizontalFadingEdgeEnabled(true);
4339 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4341 setHorizontalFadingEdgeEnabled(false);
4342 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4344 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4347 int n = mFilters.length;
4348 for (int i = 0; i < n; i++) {
4349 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4356 if (mText != null) {
4357 oldlen = mText.length();
4358 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4360 sendBeforeTextChanged("", 0, 0, text.length());
4364 boolean needEditableForNotification = false;
4366 if (mListeners != null && mListeners.size() != 0) {
4367 needEditableForNotification = true;
4370 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4371 needEditableForNotification) {
4372 createEditorIfNeeded();
4373 mEditor.forgetUndoRedo();
4374 Editable t = mEditableFactory.newEditable(text);
4376 setFilters(t, mFilters);
4377 InputMethodManager imm = InputMethodManager.peekInstance();
4378 if (imm != null) imm.restartInput(this);
4379 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4380 text = mSpannableFactory.newSpannable(text);
4381 } else if (!(text instanceof CharWrapper)) {
4382 text = TextUtils.stringOrSpannedString(text);
4385 boolean hasClickableSpans = false;
4386 if (mAutoLinkMask != 0) {
4389 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4390 s2 = (Spannable) text;
4392 s2 = mSpannableFactory.newSpannable(text);
4395 hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
4396 if (hasClickableSpans) {
4399 } else if (mLinksClickable && text instanceof Spanned) {
4400 ClickableSpan[] clickableSpans =
4401 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4402 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4403 if (hasClickableSpans && !(text instanceof Spannable)) {
4404 text = mSpannableFactory.newSpannable(text);
4408 if (hasClickableSpans) {
4409 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4411 * We must go ahead and set the text before changing the
4412 * movement method, because setMovementMethod() may call
4413 * setText() again to try to upgrade the buffer type.
4417 // Do not change the movement method for text that supports text selection as it
4418 // would prevent an arbitrary cursor displacement.
4419 if (mLinksClickable && !textCanBeSelected()) {
4420 setMovementMethod(LinkMovementMethod.getInstance());
4427 if (mTransformation == null) {
4428 mTransformed = text;
4430 mTransformed = mTransformation.getTransformation(text, this);
4433 final int textLength = text.length();
4435 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4436 Spannable sp = (Spannable) text;
4438 // Remove any ChangeWatchers that might have come from other TextViews.
4439 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4440 final int count = watchers.length;
4441 for (int i = 0; i < count; i++) {
4442 sp.removeSpan(watchers[i]);
4445 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4447 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4448 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4450 if (mEditor != null) mEditor.addSpanWatchers(sp);
4452 if (mTransformation != null) {
4453 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4456 if (mMovement != null) {
4457 mMovement.initialize(this, (Spannable) text);
4460 * Initializing the movement method will have set the
4461 * selection, so reset mSelectionMoved to keep that from
4462 * interfering with the normal on-focus selection-setting.
4464 if (mEditor != null) mEditor.mSelectionMoved = false;
4468 if (mLayout != null) {
4472 sendOnTextChanged(text, 0, oldlen, textLength);
4473 onTextChanged(text, 0, oldlen, textLength);
4475 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4477 if (needEditableForNotification) {
4478 sendAfterTextChanged((Editable) text);
4481 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4482 if (mEditor != null) mEditor.prepareCursorControllers();
4486 * Sets the TextView to display the specified slice of the specified
4487 * char array. You must promise that you will not change the contents
4488 * of the array except for right before another call to setText(),
4489 * since the TextView has no way to know that the text
4490 * has changed and that it needs to invalidate and re-layout.
4492 public final void setText(char[] text, int start, int len) {
4495 if (start < 0 || len < 0 || start + len > text.length) {
4496 throw new IndexOutOfBoundsException(start + ", " + len);
4500 * We must do the before-notification here ourselves because if
4501 * the old text is a CharWrapper we destroy it before calling
4502 * into the normal path.
4504 if (mText != null) {
4505 oldlen = mText.length();
4506 sendBeforeTextChanged(mText, 0, oldlen, len);
4508 sendBeforeTextChanged("", 0, 0, len);
4511 if (mCharWrapper == null) {
4512 mCharWrapper = new CharWrapper(text, start, len);
4514 mCharWrapper.set(text, start, len);
4517 setText(mCharWrapper, mBufferType, false, oldlen);
4521 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4522 * except that the cursor position (if any) is retained in the new text.
4524 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4526 public final void setTextKeepState(CharSequence text, BufferType type) {
4527 int start = getSelectionStart();
4528 int end = getSelectionEnd();
4529 int len = text.length();
4531 setText(text, type);
4533 if (start >= 0 || end >= 0) {
4534 if (mText instanceof Spannable) {
4535 Selection.setSelection((Spannable) mText,
4536 Math.max(0, Math.min(start, len)),
4537 Math.max(0, Math.min(end, len)));
4542 @android.view.RemotableViewMethod
4543 public final void setText(@StringRes int resid) {
4544 setText(getContext().getResources().getText(resid));
4547 public final void setText(@StringRes int resid, BufferType type) {
4548 setText(getContext().getResources().getText(resid), type);
4552 * Sets the text to be displayed when the text of the TextView is empty.
4553 * Null means to use the normal empty text. The hint does not currently
4554 * participate in determining the size of the view.
4556 * @attr ref android.R.styleable#TextView_hint
4558 @android.view.RemotableViewMethod
4559 public final void setHint(CharSequence hint) {
4560 mHint = TextUtils.stringOrSpannedString(hint);
4562 if (mLayout != null) {
4566 if (mText.length() == 0) {
4570 // Invalidate display list if hint is currently used
4571 if (mEditor != null && mText.length() == 0 && mHint != null) {
4572 mEditor.invalidateTextDisplayList();
4577 * Sets the text to be displayed when the text of the TextView is empty,
4580 * @attr ref android.R.styleable#TextView_hint
4582 @android.view.RemotableViewMethod
4583 public final void setHint(@StringRes int resid) {
4584 setHint(getContext().getResources().getText(resid));
4588 * Returns the hint that is displayed when the text of the TextView
4591 * @attr ref android.R.styleable#TextView_hint
4593 @ViewDebug.CapturedViewProperty
4594 public CharSequence getHint() {
4598 boolean isSingleLine() {
4602 private static boolean isMultilineInputType(int type) {
4603 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4604 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4608 * Removes the suggestion spans.
4610 CharSequence removeSuggestionSpans(CharSequence text) {
4611 if (text instanceof Spanned) {
4612 Spannable spannable;
4613 if (text instanceof Spannable) {
4614 spannable = (Spannable) text;
4616 spannable = new SpannableString(text);
4620 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4621 for (int i = 0; i < spans.length; i++) {
4622 spannable.removeSpan(spans[i]);
4629 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4630 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4631 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4632 * then a soft keyboard will not be displayed for this text view.
4634 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4635 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4638 * @see #getInputType()
4639 * @see #setRawInputType(int)
4640 * @see android.text.InputType
4641 * @attr ref android.R.styleable#TextView_inputType
4643 public void setInputType(int type) {
4644 final boolean wasPassword = isPasswordInputType(getInputType());
4645 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4646 setInputType(type, false);
4647 final boolean isPassword = isPasswordInputType(type);
4648 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4649 boolean forceUpdate = false;
4651 setTransformationMethod(PasswordTransformationMethod.getInstance());
4652 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4653 } else if (isVisiblePassword) {
4654 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4657 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4658 } else if (wasPassword || wasVisiblePassword) {
4659 // not in password mode, clean up typeface and transformation
4660 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4661 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4666 boolean singleLine = !isMultilineInputType(type);
4668 // We need to update the single line mode if it has changed or we
4669 // were previously in password mode.
4670 if (mSingleLine != singleLine || forceUpdate) {
4671 // Change single line mode, but only change the transformation if
4672 // we are not in password mode.
4673 applySingleLine(singleLine, !isPassword, true);
4676 if (!isSuggestionsEnabled()) {
4677 mText = removeSuggestionSpans(mText);
4680 InputMethodManager imm = InputMethodManager.peekInstance();
4681 if (imm != null) imm.restartInput(this);
4685 * It would be better to rely on the input type for everything. A password inputType should have
4686 * a password transformation. We should hence use isPasswordInputType instead of this method.
4689 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4690 * would install the correct transformation).
4691 * - Refuse the installation of a non-password transformation in setTransformation if the input
4694 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4695 * is useful since it matches what the user can see (obfuscated text or not).
4697 * @return true if the current transformation method is of the password type.
4699 boolean hasPasswordTransformationMethod() {
4700 return mTransformation instanceof PasswordTransformationMethod;
4703 private static boolean isPasswordInputType(int inputType) {
4704 final int variation =
4705 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4707 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4709 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4711 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4714 private static boolean isVisiblePasswordInputType(int inputType) {
4715 final int variation =
4716 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4718 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4722 * Directly change the content type integer of the text view, without
4723 * modifying any other state.
4724 * @see #setInputType(int)
4725 * @see android.text.InputType
4726 * @attr ref android.R.styleable#TextView_inputType
4728 public void setRawInputType(int type) {
4729 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4730 createEditorIfNeeded();
4731 mEditor.mInputType = type;
4734 private void setInputType(int type, boolean direct) {
4735 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4737 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4738 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4739 TextKeyListener.Capitalize cap;
4740 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4741 cap = TextKeyListener.Capitalize.CHARACTERS;
4742 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4743 cap = TextKeyListener.Capitalize.WORDS;
4744 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4745 cap = TextKeyListener.Capitalize.SENTENCES;
4747 cap = TextKeyListener.Capitalize.NONE;
4749 input = TextKeyListener.getInstance(autotext, cap);
4750 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4751 input = DigitsKeyListener.getInstance(
4752 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4753 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4754 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4755 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4756 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4757 input = DateKeyListener.getInstance();
4759 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4760 input = TimeKeyListener.getInstance();
4763 input = DateTimeKeyListener.getInstance();
4766 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4767 input = DialerKeyListener.getInstance();
4769 input = TextKeyListener.getInstance();
4771 setRawInputType(type);
4773 createEditorIfNeeded();
4774 mEditor.mKeyListener = input;
4776 setKeyListenerOnly(input);
4781 * Get the type of the editable content.
4783 * @see #setInputType(int)
4784 * @see android.text.InputType
4786 public int getInputType() {
4787 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4791 * Change the editor type integer associated with the text view, which
4792 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4794 * @see #getImeOptions
4795 * @see android.view.inputmethod.EditorInfo
4796 * @attr ref android.R.styleable#TextView_imeOptions
4798 public void setImeOptions(int imeOptions) {
4799 createEditorIfNeeded();
4800 mEditor.createInputContentTypeIfNeeded();
4801 mEditor.mInputContentType.imeOptions = imeOptions;
4805 * Get the type of the IME editor.
4807 * @see #setImeOptions(int)
4808 * @see android.view.inputmethod.EditorInfo
4810 public int getImeOptions() {
4811 return mEditor != null && mEditor.mInputContentType != null
4812 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4816 * Change the custom IME action associated with the text view, which
4817 * will be reported to an IME with {@link EditorInfo#actionLabel}
4818 * and {@link EditorInfo#actionId} when it has focus.
4819 * @see #getImeActionLabel
4820 * @see #getImeActionId
4821 * @see android.view.inputmethod.EditorInfo
4822 * @attr ref android.R.styleable#TextView_imeActionLabel
4823 * @attr ref android.R.styleable#TextView_imeActionId
4825 public void setImeActionLabel(CharSequence label, int actionId) {
4826 createEditorIfNeeded();
4827 mEditor.createInputContentTypeIfNeeded();
4828 mEditor.mInputContentType.imeActionLabel = label;
4829 mEditor.mInputContentType.imeActionId = actionId;
4833 * Get the IME action label previous set with {@link #setImeActionLabel}.
4835 * @see #setImeActionLabel
4836 * @see android.view.inputmethod.EditorInfo
4838 public CharSequence getImeActionLabel() {
4839 return mEditor != null && mEditor.mInputContentType != null
4840 ? mEditor.mInputContentType.imeActionLabel : null;
4844 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4846 * @see #setImeActionLabel
4847 * @see android.view.inputmethod.EditorInfo
4849 public int getImeActionId() {
4850 return mEditor != null && mEditor.mInputContentType != null
4851 ? mEditor.mInputContentType.imeActionId : 0;
4855 * Set a special listener to be called when an action is performed
4856 * on the text view. This will be called when the enter key is pressed,
4857 * or when an action supplied to the IME is selected by the user. Setting
4858 * this means that the normal hard key event will not insert a newline
4859 * into the text view, even if it is multi-line; holding down the ALT
4860 * modifier will, however, allow the user to insert a newline character.
4862 public void setOnEditorActionListener(OnEditorActionListener l) {
4863 createEditorIfNeeded();
4864 mEditor.createInputContentTypeIfNeeded();
4865 mEditor.mInputContentType.onEditorActionListener = l;
4869 * Called when an attached input method calls
4870 * {@link InputConnection#performEditorAction(int)
4871 * InputConnection.performEditorAction()}
4872 * for this text view. The default implementation will call your action
4873 * listener supplied to {@link #setOnEditorActionListener}, or perform
4874 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4875 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4876 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4877 * EditorInfo.IME_ACTION_DONE}.
4879 * <p>For backwards compatibility, if no IME options have been set and the
4880 * text view would not normally advance focus on enter, then
4881 * the NEXT and DONE actions received here will be turned into an enter
4882 * key down/up pair to go through the normal key handling.
4884 * @param actionCode The code of the action being performed.
4886 * @see #setOnEditorActionListener
4888 public void onEditorAction(int actionCode) {
4889 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4891 if (ict.onEditorActionListener != null) {
4892 if (ict.onEditorActionListener.onEditorAction(this,
4893 actionCode, null)) {
4898 // This is the handling for some default action.
4899 // Note that for backwards compatibility we don't do this
4900 // default handling if explicit ime options have not been given,
4901 // instead turning this into the normal enter key codes that an
4902 // app may be expecting.
4903 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4904 View v = focusSearch(FOCUS_FORWARD);
4906 if (!v.requestFocus(FOCUS_FORWARD)) {
4907 throw new IllegalStateException("focus search returned a view " +
4908 "that wasn't able to take focus!");
4913 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4914 View v = focusSearch(FOCUS_BACKWARD);
4916 if (!v.requestFocus(FOCUS_BACKWARD)) {
4917 throw new IllegalStateException("focus search returned a view " +
4918 "that wasn't able to take focus!");
4923 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4924 InputMethodManager imm = InputMethodManager.peekInstance();
4925 if (imm != null && imm.isActive(this)) {
4926 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4932 ViewRootImpl viewRootImpl = getViewRootImpl();
4933 if (viewRootImpl != null) {
4934 long eventTime = SystemClock.uptimeMillis();
4935 viewRootImpl.dispatchKeyFromIme(
4936 new KeyEvent(eventTime, eventTime,
4937 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4938 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4939 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4940 | KeyEvent.FLAG_EDITOR_ACTION));
4941 viewRootImpl.dispatchKeyFromIme(
4942 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4943 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4944 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4945 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4946 | KeyEvent.FLAG_EDITOR_ACTION));
4951 * Set the private content type of the text, which is the
4952 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4953 * field that will be filled in when creating an input connection.
4955 * @see #getPrivateImeOptions()
4956 * @see EditorInfo#privateImeOptions
4957 * @attr ref android.R.styleable#TextView_privateImeOptions
4959 public void setPrivateImeOptions(String type) {
4960 createEditorIfNeeded();
4961 mEditor.createInputContentTypeIfNeeded();
4962 mEditor.mInputContentType.privateImeOptions = type;
4966 * Get the private type of the content.
4968 * @see #setPrivateImeOptions(String)
4969 * @see EditorInfo#privateImeOptions
4971 public String getPrivateImeOptions() {
4972 return mEditor != null && mEditor.mInputContentType != null
4973 ? mEditor.mInputContentType.privateImeOptions : null;
4977 * Set the extra input data of the text, which is the
4978 * {@link EditorInfo#extras TextBoxAttribute.extras}
4979 * Bundle that will be filled in when creating an input connection. The
4980 * given integer is the resource ID of an XML resource holding an
4981 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4983 * @see #getInputExtras(boolean)
4984 * @see EditorInfo#extras
4985 * @attr ref android.R.styleable#TextView_editorExtras
4987 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4988 createEditorIfNeeded();
4989 XmlResourceParser parser = getResources().getXml(xmlResId);
4990 mEditor.createInputContentTypeIfNeeded();
4991 mEditor.mInputContentType.extras = new Bundle();
4992 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
4996 * Retrieve the input extras currently associated with the text view, which
4997 * can be viewed as well as modified.
4999 * @param create If true, the extras will be created if they don't already
5000 * exist. Otherwise, null will be returned if none have been created.
5001 * @see #setInputExtras(int)
5002 * @see EditorInfo#extras
5003 * @attr ref android.R.styleable#TextView_editorExtras
5005 public Bundle getInputExtras(boolean create) {
5006 if (mEditor == null && !create) return null;
5007 createEditorIfNeeded();
5008 if (mEditor.mInputContentType == null) {
5009 if (!create) return null;
5010 mEditor.createInputContentTypeIfNeeded();
5012 if (mEditor.mInputContentType.extras == null) {
5013 if (!create) return null;
5014 mEditor.mInputContentType.extras = new Bundle();
5016 return mEditor.mInputContentType.extras;
5020 * Change "hint" locales associated with the text view, which will be reported to an IME with
5021 * {@link EditorInfo#hintLocales} when it has focus.
5023 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5024 * call {@link InputMethodManager#restartInput(View)}.</p>
5025 * @param hintLocales List of the languages that the user is supposed to switch to no matter
5026 * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5027 * @see #getImeHIntLocales()
5028 * @see android.view.inputmethod.EditorInfo#hintLocales
5030 public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5031 createEditorIfNeeded();
5032 mEditor.createInputContentTypeIfNeeded();
5033 mEditor.mInputContentType.imeHintLocales = hintLocales;
5037 * @return The current languages list "hint". {@code null} when no "hint" is available.
5038 * @see #setImeHintLocales(LocaleList)
5039 * @see android.view.inputmethod.EditorInfo#hintLocales
5042 public LocaleList getImeHintLocales() {
5043 if (mEditor == null) { return null; }
5044 if (mEditor.mInputContentType == null) { return null; }
5045 return mEditor.mInputContentType.imeHintLocales;
5049 * Returns the error message that was set to be displayed with
5050 * {@link #setError}, or <code>null</code> if no error was set
5051 * or if it the error was cleared by the widget after user input.
5053 public CharSequence getError() {
5054 return mEditor == null ? null : mEditor.mError;
5058 * Sets the right-hand compound drawable of the TextView to the "error"
5059 * icon and sets an error message that will be displayed in a popup when
5060 * the TextView has focus. The icon and error message will be reset to
5061 * null when any key events cause changes to the TextView's text. If the
5062 * <code>error</code> is <code>null</code>, the error message and icon
5065 @android.view.RemotableViewMethod
5066 public void setError(CharSequence error) {
5067 if (error == null) {
5068 setError(null, null);
5070 Drawable dr = getContext().getDrawable(
5071 com.android.internal.R.drawable.indicator_input_error);
5073 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5074 setError(error, dr);
5079 * Sets the right-hand compound drawable of the TextView to the specified
5080 * icon and sets an error message that will be displayed in a popup when
5081 * the TextView has focus. The icon and error message will be reset to
5082 * null when any key events cause changes to the TextView's text. The
5083 * drawable must already have had {@link Drawable#setBounds} set on it.
5084 * If the <code>error</code> is <code>null</code>, the error message will
5085 * be cleared (and you should provide a <code>null</code> icon as well).
5087 public void setError(CharSequence error, Drawable icon) {
5088 createEditorIfNeeded();
5089 mEditor.setError(error, icon);
5090 notifyViewAccessibilityStateChangedIfNeeded(
5091 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
5095 protected boolean setFrame(int l, int t, int r, int b) {
5096 boolean result = super.setFrame(l, t, r, b);
5098 if (mEditor != null) mEditor.setFrame();
5100 restartMarqueeIfNeeded();
5105 private void restartMarqueeIfNeeded() {
5106 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5107 mRestartMarquee = false;
5113 * Sets the list of input filters that will be used if the buffer is
5114 * Editable. Has no effect otherwise.
5116 * @attr ref android.R.styleable#TextView_maxLength
5118 public void setFilters(InputFilter[] filters) {
5119 if (filters == null) {
5120 throw new IllegalArgumentException();
5125 if (mText instanceof Editable) {
5126 setFilters((Editable) mText, filters);
5131 * Sets the list of input filters on the specified Editable,
5132 * and includes mInput in the list if it is an InputFilter.
5134 private void setFilters(Editable e, InputFilter[] filters) {
5135 if (mEditor != null) {
5136 final boolean undoFilter = mEditor.mUndoInputFilter != null;
5137 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5139 if (undoFilter) num++;
5140 if (keyFilter) num++;
5142 InputFilter[] nf = new InputFilter[filters.length + num];
5144 System.arraycopy(filters, 0, nf, 0, filters.length);
5147 nf[filters.length] = mEditor.mUndoInputFilter;
5151 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5158 e.setFilters(filters);
5162 * Returns the current list of input filters.
5164 * @attr ref android.R.styleable#TextView_maxLength
5166 public InputFilter[] getFilters() {
5170 /////////////////////////////////////////////////////////////////////////
5172 private int getBoxHeight(Layout l) {
5173 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5174 int padding = (l == mHintLayout) ?
5175 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5176 getExtendedPaddingTop() + getExtendedPaddingBottom();
5177 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5180 int getVerticalOffset(boolean forceNormal) {
5182 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5185 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5189 if (gravity != Gravity.TOP) {
5190 int boxht = getBoxHeight(l);
5191 int textht = l.getHeight();
5193 if (textht < boxht) {
5194 if (gravity == Gravity.BOTTOM)
5195 voffset = boxht - textht;
5196 else // (gravity == Gravity.CENTER_VERTICAL)
5197 voffset = (boxht - textht) >> 1;
5203 private int getBottomVerticalOffset(boolean forceNormal) {
5205 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5208 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5212 if (gravity != Gravity.BOTTOM) {
5213 int boxht = getBoxHeight(l);
5214 int textht = l.getHeight();
5216 if (textht < boxht) {
5217 if (gravity == Gravity.TOP)
5218 voffset = boxht - textht;
5219 else // (gravity == Gravity.CENTER_VERTICAL)
5220 voffset = (boxht - textht) >> 1;
5226 void invalidateCursorPath() {
5227 if (mHighlightPathBogus) {
5230 final int horizontalPadding = getCompoundPaddingLeft();
5231 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5233 if (mEditor.mCursorCount == 0) {
5234 synchronized (TEMP_RECTF) {
5236 * The reason for this concern about the thickness of the
5237 * cursor and doing the floor/ceil on the coordinates is that
5238 * some EditTexts (notably textfields in the Browser) have
5239 * anti-aliased text where not all the characters are
5240 * necessarily at integer-multiple locations. This should
5241 * make sure the entire cursor gets invalidated instead of
5242 * sometimes missing half a pixel.
5244 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5251 // mHighlightPath is guaranteed to be non null at that point.
5252 mHighlightPath.computeBounds(TEMP_RECTF, false);
5254 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5255 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5256 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5257 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5260 for (int i = 0; i < mEditor.mCursorCount; i++) {
5261 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5262 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5263 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5269 void invalidateCursor() {
5270 int where = getSelectionEnd();
5272 invalidateCursor(where, where, where);
5275 private void invalidateCursor(int a, int b, int c) {
5276 if (a >= 0 || b >= 0 || c >= 0) {
5277 int start = Math.min(Math.min(a, b), c);
5278 int end = Math.max(Math.max(a, b), c);
5279 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5284 * Invalidates the region of text enclosed between the start and end text offsets.
5286 void invalidateRegion(int start, int end, boolean invalidateCursor) {
5287 if (mLayout == null) {
5290 int lineStart = mLayout.getLineForOffset(start);
5291 int top = mLayout.getLineTop(lineStart);
5293 // This is ridiculous, but the descent from the line above
5294 // can hang down into the line we really want to redraw,
5295 // so we have to invalidate part of the line above to make
5296 // sure everything that needs to be redrawn really is.
5297 // (But not the whole line above, because that would cause
5298 // the same problem with the descenders on the line above it!)
5299 if (lineStart > 0) {
5300 top -= mLayout.getLineDescent(lineStart - 1);
5306 lineEnd = lineStart;
5308 lineEnd = mLayout.getLineForOffset(end);
5310 int bottom = mLayout.getLineBottom(lineEnd);
5312 // mEditor can be null in case selection is set programmatically.
5313 if (invalidateCursor && mEditor != null) {
5314 for (int i = 0; i < mEditor.mCursorCount; i++) {
5315 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5316 top = Math.min(top, bounds.top);
5317 bottom = Math.max(bottom, bounds.bottom);
5321 final int compoundPaddingLeft = getCompoundPaddingLeft();
5322 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5325 if (lineStart == lineEnd && !invalidateCursor) {
5326 left = (int) mLayout.getPrimaryHorizontal(start);
5327 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5328 left += compoundPaddingLeft;
5329 right += compoundPaddingLeft;
5331 // Rectangle bounding box when the region spans several lines
5332 left = compoundPaddingLeft;
5333 right = getWidth() - getCompoundPaddingRight();
5336 invalidate(mScrollX + left, verticalPadding + top,
5337 mScrollX + right, verticalPadding + bottom);
5341 private void registerForPreDraw() {
5342 if (!mPreDrawRegistered) {
5343 getViewTreeObserver().addOnPreDrawListener(this);
5344 mPreDrawRegistered = true;
5348 private void unregisterForPreDraw() {
5349 getViewTreeObserver().removeOnPreDrawListener(this);
5350 mPreDrawRegistered = false;
5351 mPreDrawListenerDetached = false;
5357 public boolean onPreDraw() {
5358 if (mLayout == null) {
5362 if (mMovement != null) {
5363 /* This code also provides auto-scrolling when a cursor is moved using a
5364 * CursorController (insertion point or selection limits).
5365 * For selection, ensure start or end is visible depending on controller's state.
5367 int curs = getSelectionEnd();
5368 // Do not create the controller if it is not already created.
5369 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5370 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5371 curs = getSelectionStart();
5375 * TODO: This should really only keep the end in view if
5376 * it already was before the text changed. I'm not sure
5377 * of a good way to tell from here if it was.
5379 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5380 curs = mText.length();
5384 bringPointIntoView(curs);
5387 bringTextIntoView();
5390 // This has to be checked here since:
5391 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5392 // a screen rotation) since layout is not yet initialized at that point.
5393 if (mEditor != null && mEditor.mCreatedWithASelection) {
5394 if (mEditor.extractedTextModeWillBeStarted()) {
5395 mEditor.checkFieldAndSelectCurrentWord();
5397 mEditor.startSelectionActionMode();
5399 mEditor.mCreatedWithASelection = false;
5402 unregisterForPreDraw();
5408 protected void onAttachedToWindow() {
5409 super.onAttachedToWindow();
5411 mTemporaryDetach = false;
5413 if (mEditor != null) mEditor.onAttachedToWindow();
5415 if (mPreDrawListenerDetached) {
5416 getViewTreeObserver().addOnPreDrawListener(this);
5417 mPreDrawListenerDetached = false;
5423 protected void onDetachedFromWindowInternal() {
5424 if (mPreDrawRegistered) {
5425 getViewTreeObserver().removeOnPreDrawListener(this);
5426 mPreDrawListenerDetached = true;
5429 resetResolvedDrawables();
5431 if (mEditor != null) mEditor.onDetachedFromWindow();
5433 super.onDetachedFromWindowInternal();
5437 public void onScreenStateChanged(int screenState) {
5438 super.onScreenStateChanged(screenState);
5439 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5443 protected boolean isPaddingOffsetRequired() {
5444 return mShadowRadius != 0 || mDrawables != null;
5448 protected int getLeftPaddingOffset() {
5449 return getCompoundPaddingLeft() - mPaddingLeft +
5450 (int) Math.min(0, mShadowDx - mShadowRadius);
5454 protected int getTopPaddingOffset() {
5455 return (int) Math.min(0, mShadowDy - mShadowRadius);
5459 protected int getBottomPaddingOffset() {
5460 return (int) Math.max(0, mShadowDy + mShadowRadius);
5464 protected int getRightPaddingOffset() {
5465 return -(getCompoundPaddingRight() - mPaddingRight) +
5466 (int) Math.max(0, mShadowDx + mShadowRadius);
5470 protected boolean verifyDrawable(Drawable who) {
5471 final boolean verified = super.verifyDrawable(who);
5472 if (!verified && mDrawables != null) {
5473 for (Drawable dr : mDrawables.mShowing) {
5483 public void jumpDrawablesToCurrentState() {
5484 super.jumpDrawablesToCurrentState();
5485 if (mDrawables != null) {
5486 for (Drawable dr : mDrawables.mShowing) {
5488 dr.jumpToCurrentState();
5495 public void invalidateDrawable(Drawable drawable) {
5496 boolean handled = false;
5498 if (verifyDrawable(drawable)) {
5499 final Rect dirty = drawable.getBounds();
5500 int scrollX = mScrollX;
5501 int scrollY = mScrollY;
5503 // IMPORTANT: The coordinates below are based on the coordinates computed
5504 // for each compound drawable in onDraw(). Make sure to update each section
5506 final TextView.Drawables drawables = mDrawables;
5507 if (drawables != null) {
5508 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5509 final int compoundPaddingTop = getCompoundPaddingTop();
5510 final int compoundPaddingBottom = getCompoundPaddingBottom();
5511 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5513 scrollX += mPaddingLeft;
5514 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5516 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5517 final int compoundPaddingTop = getCompoundPaddingTop();
5518 final int compoundPaddingBottom = getCompoundPaddingBottom();
5519 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5521 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5522 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5524 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5525 final int compoundPaddingLeft = getCompoundPaddingLeft();
5526 final int compoundPaddingRight = getCompoundPaddingRight();
5527 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5529 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5530 scrollY += mPaddingTop;
5532 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5533 final int compoundPaddingLeft = getCompoundPaddingLeft();
5534 final int compoundPaddingRight = getCompoundPaddingRight();
5535 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5537 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5538 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5544 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5545 dirty.right + scrollX, dirty.bottom + scrollY);
5550 super.invalidateDrawable(drawable);
5555 public boolean hasOverlappingRendering() {
5556 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5557 return ((getBackground() != null && getBackground().getCurrent() != null)
5558 || mText instanceof Spannable || hasSelection()
5559 || isHorizontalFadingEdgeEnabled());
5564 * Returns the state of the {@code textIsSelectable} flag (See
5565 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5566 * to allow users to select and copy text in a non-editable TextView, the content of an
5567 * {@link EditText} can always be selected, independently of the value of this flag.
5570 * @return True if the text displayed in this TextView can be selected by the user.
5572 * @attr ref android.R.styleable#TextView_textIsSelectable
5574 public boolean isTextSelectable() {
5575 return mEditor == null ? false : mEditor.mTextIsSelectable;
5579 * Sets whether the content of this view is selectable by the user. The default is
5580 * {@code false}, meaning that the content is not selectable.
5582 * When you use a TextView to display a useful piece of information to the user (such as a
5583 * contact's address), make it selectable, so that the user can select and copy its
5584 * content. You can also use set the XML attribute
5585 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5587 * When you call this method to set the value of {@code textIsSelectable}, it sets
5588 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5589 * and {@code longClickable} to the same value. These flags correspond to the attributes
5590 * {@link android.R.styleable#View_focusable android:focusable},
5591 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5592 * {@link android.R.styleable#View_clickable android:clickable}, and
5593 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5594 * flags to a state you had set previously, call one or more of the following methods:
5595 * {@link #setFocusable(boolean) setFocusable()},
5596 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5597 * {@link #setClickable(boolean) setClickable()} or
5598 * {@link #setLongClickable(boolean) setLongClickable()}.
5600 * @param selectable Whether the content of this TextView should be selectable.
5602 public void setTextIsSelectable(boolean selectable) {
5603 if (!selectable && mEditor == null) return; // false is default value with no edit data
5605 createEditorIfNeeded();
5606 if (mEditor.mTextIsSelectable == selectable) return;
5608 mEditor.mTextIsSelectable = selectable;
5609 setFocusableInTouchMode(selectable);
5610 setFocusable(selectable);
5611 setClickable(selectable);
5612 setLongClickable(selectable);
5614 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5616 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5617 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5619 // Called by setText above, but safer in case of future code changes
5620 mEditor.prepareCursorControllers();
5624 protected int[] onCreateDrawableState(int extraSpace) {
5625 final int[] drawableState;
5628 drawableState = super.onCreateDrawableState(extraSpace);
5630 drawableState = super.onCreateDrawableState(extraSpace + 1);
5631 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5634 if (isTextSelectable()) {
5635 // Disable pressed state, which was introduced when TextView was made clickable.
5636 // Prevents text color change.
5637 // setClickable(false) would have a similar effect, but it also disables focus changes
5638 // and long press actions, which are both needed by text selection.
5639 final int length = drawableState.length;
5640 for (int i = 0; i < length; i++) {
5641 if (drawableState[i] == R.attr.state_pressed) {
5642 final int[] nonPressedState = new int[length - 1];
5643 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5644 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5645 return nonPressedState;
5650 return drawableState;
5653 private Path getUpdatedHighlightPath() {
5654 Path highlight = null;
5655 Paint highlightPaint = mHighlightPaint;
5657 final int selStart = getSelectionStart();
5658 final int selEnd = getSelectionEnd();
5659 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5660 if (selStart == selEnd) {
5661 if (mEditor != null && mEditor.isCursorVisible() &&
5662 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5663 (2 * Editor.BLINK) < Editor.BLINK) {
5664 if (mHighlightPathBogus) {
5665 if (mHighlightPath == null) mHighlightPath = new Path();
5666 mHighlightPath.reset();
5667 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5668 mEditor.updateCursorsPositions();
5669 mHighlightPathBogus = false;
5672 // XXX should pass to skin instead of drawing directly
5673 highlightPaint.setColor(mCurTextColor);
5674 highlightPaint.setStyle(Paint.Style.STROKE);
5675 highlight = mHighlightPath;
5678 if (mHighlightPathBogus) {
5679 if (mHighlightPath == null) mHighlightPath = new Path();
5680 mHighlightPath.reset();
5681 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5682 mHighlightPathBogus = false;
5685 // XXX should pass to skin instead of drawing directly
5686 highlightPaint.setColor(mHighlightColor);
5687 highlightPaint.setStyle(Paint.Style.FILL);
5689 highlight = mHighlightPath;
5698 public int getHorizontalOffsetForDrawables() {
5703 protected void onDraw(Canvas canvas) {
5704 restartMarqueeIfNeeded();
5706 // Draw the background for this view
5707 super.onDraw(canvas);
5709 final int compoundPaddingLeft = getCompoundPaddingLeft();
5710 final int compoundPaddingTop = getCompoundPaddingTop();
5711 final int compoundPaddingRight = getCompoundPaddingRight();
5712 final int compoundPaddingBottom = getCompoundPaddingBottom();
5713 final int scrollX = mScrollX;
5714 final int scrollY = mScrollY;
5715 final int right = mRight;
5716 final int left = mLeft;
5717 final int bottom = mBottom;
5718 final int top = mTop;
5719 final boolean isLayoutRtl = isLayoutRtl();
5720 final int offset = getHorizontalOffsetForDrawables();
5721 final int leftOffset = isLayoutRtl ? 0 : offset;
5722 final int rightOffset = isLayoutRtl ? offset : 0 ;
5724 final Drawables dr = mDrawables;
5727 * Compound, not extended, because the icon is not clipped
5728 * if the text height is smaller.
5731 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5732 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5734 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5735 // Make sure to update invalidateDrawable() when changing this code.
5736 if (dr.mShowing[Drawables.LEFT] != null) {
5738 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5739 scrollY + compoundPaddingTop +
5740 (vspace - dr.mDrawableHeightLeft) / 2);
5741 dr.mShowing[Drawables.LEFT].draw(canvas);
5745 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5746 // Make sure to update invalidateDrawable() when changing this code.
5747 if (dr.mShowing[Drawables.RIGHT] != null) {
5749 canvas.translate(scrollX + right - left - mPaddingRight
5750 - dr.mDrawableSizeRight - rightOffset,
5751 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5752 dr.mShowing[Drawables.RIGHT].draw(canvas);
5756 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5757 // Make sure to update invalidateDrawable() when changing this code.
5758 if (dr.mShowing[Drawables.TOP] != null) {
5760 canvas.translate(scrollX + compoundPaddingLeft +
5761 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5762 dr.mShowing[Drawables.TOP].draw(canvas);
5766 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5767 // Make sure to update invalidateDrawable() when changing this code.
5768 if (dr.mShowing[Drawables.BOTTOM] != null) {
5770 canvas.translate(scrollX + compoundPaddingLeft +
5771 (hspace - dr.mDrawableWidthBottom) / 2,
5772 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5773 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5778 int color = mCurTextColor;
5780 if (mLayout == null) {
5784 Layout layout = mLayout;
5786 if (mHint != null && mText.length() == 0) {
5787 if (mHintTextColor != null) {
5788 color = mCurHintTextColor;
5791 layout = mHintLayout;
5794 mTextPaint.setColor(color);
5795 mTextPaint.drawableState = getDrawableState();
5798 /* Would be faster if we didn't have to do this. Can we chop the
5799 (displayable) text so that we don't need to do this ever?
5802 int extendedPaddingTop = getExtendedPaddingTop();
5803 int extendedPaddingBottom = getExtendedPaddingBottom();
5805 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5806 final int maxScrollY = mLayout.getHeight() - vspace;
5808 float clipLeft = compoundPaddingLeft + scrollX;
5809 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5810 float clipRight = right - left - getCompoundPaddingRight() + scrollX;
5811 float clipBottom = bottom - top + scrollY -
5812 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5814 if (mShadowRadius != 0) {
5815 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5816 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5818 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5819 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5822 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5824 int voffsetText = 0;
5825 int voffsetCursor = 0;
5827 // translate in by our padding
5828 /* shortcircuit calling getVerticaOffset() */
5829 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5830 voffsetText = getVerticalOffset(false);
5831 voffsetCursor = getVerticalOffset(true);
5833 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5835 final int layoutDirection = getLayoutDirection();
5836 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5837 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5838 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5839 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5840 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5841 final int width = mRight - mLeft;
5842 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5843 final float dx = mLayout.getLineRight(0) - (width - padding);
5844 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5847 if (mMarquee != null && mMarquee.isRunning()) {
5848 final float dx = -mMarquee.getScroll();
5849 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5853 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5855 Path highlight = getUpdatedHighlightPath();
5856 if (mEditor != null) {
5857 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5859 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5862 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5863 final float dx = mMarquee.getGhostOffset();
5864 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5865 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5872 public void getFocusedRect(Rect r) {
5873 if (mLayout == null) {
5874 super.getFocusedRect(r);
5878 int selEnd = getSelectionEnd();
5880 super.getFocusedRect(r);
5884 int selStart = getSelectionStart();
5885 if (selStart < 0 || selStart >= selEnd) {
5886 int line = mLayout.getLineForOffset(selEnd);
5887 r.top = mLayout.getLineTop(line);
5888 r.bottom = mLayout.getLineBottom(line);
5889 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5890 r.right = r.left + 4;
5892 int lineStart = mLayout.getLineForOffset(selStart);
5893 int lineEnd = mLayout.getLineForOffset(selEnd);
5894 r.top = mLayout.getLineTop(lineStart);
5895 r.bottom = mLayout.getLineBottom(lineEnd);
5896 if (lineStart == lineEnd) {
5897 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5898 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5900 // Selection extends across multiple lines -- make the focused
5901 // rect cover the entire width.
5902 if (mHighlightPathBogus) {
5903 if (mHighlightPath == null) mHighlightPath = new Path();
5904 mHighlightPath.reset();
5905 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5906 mHighlightPathBogus = false;
5908 synchronized (TEMP_RECTF) {
5909 mHighlightPath.computeBounds(TEMP_RECTF, true);
5910 r.left = (int)TEMP_RECTF.left-1;
5911 r.right = (int)TEMP_RECTF.right+1;
5916 // Adjust for padding and gravity.
5917 int paddingLeft = getCompoundPaddingLeft();
5918 int paddingTop = getExtendedPaddingTop();
5919 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5920 paddingTop += getVerticalOffset(false);
5922 r.offset(paddingLeft, paddingTop);
5923 int paddingBottom = getExtendedPaddingBottom();
5924 r.bottom += paddingBottom;
5928 * Return the number of lines of text, or 0 if the internal Layout has not
5931 public int getLineCount() {
5932 return mLayout != null ? mLayout.getLineCount() : 0;
5936 * Return the baseline for the specified line (0...getLineCount() - 1)
5937 * If bounds is not null, return the top, left, right, bottom extents
5938 * of the specified line in it. If the internal Layout has not been built,
5939 * return 0 and set bounds to (0, 0, 0, 0)
5940 * @param line which line to examine (0..getLineCount() - 1)
5941 * @param bounds Optional. If not null, it returns the extent of the line
5942 * @return the Y-coordinate of the baseline
5944 public int getLineBounds(int line, Rect bounds) {
5945 if (mLayout == null) {
5946 if (bounds != null) {
5947 bounds.set(0, 0, 0, 0);
5952 int baseline = mLayout.getLineBounds(line, bounds);
5954 int voffset = getExtendedPaddingTop();
5955 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5956 voffset += getVerticalOffset(true);
5958 if (bounds != null) {
5959 bounds.offset(getCompoundPaddingLeft(), voffset);
5961 return baseline + voffset;
5966 public int getBaseline() {
5967 if (mLayout == null) {
5968 return super.getBaseline();
5971 return getBaselineOffset() + mLayout.getLineBaseline(0);
5974 int getBaselineOffset() {
5976 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5977 voffset = getVerticalOffset(true);
5980 if (isLayoutModeOptical(mParent)) {
5981 voffset -= getOpticalInsets().top;
5984 return getExtendedPaddingTop() + voffset;
5991 protected int getFadeTop(boolean offsetRequired) {
5992 if (mLayout == null) return 0;
5995 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5996 voffset = getVerticalOffset(true);
5999 if (offsetRequired) voffset += getTopPaddingOffset();
6001 return getExtendedPaddingTop() + voffset;
6008 protected int getFadeHeight(boolean offsetRequired) {
6009 return mLayout != null ? mLayout.getHeight() : 0;
6013 public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
6014 if (mText instanceof Spannable && mLinksClickable) {
6015 final int offset = getOffsetForPosition(x, y);
6016 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6017 ClickableSpan.class);
6018 if (clickables.length > 0) {
6019 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_HAND);
6022 if (isTextSelectable() || isTextEditable()) {
6023 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_TEXT);
6025 return super.getPointerIcon(event, x, y);
6029 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
6030 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6031 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6032 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6033 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6036 return super.onKeyPreIme(keyCode, event);
6042 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6043 // Do nothing unless mEditor is in text action mode.
6044 if (mEditor == null || mEditor.mTextActionMode == null) {
6048 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6049 KeyEvent.DispatcherState state = getKeyDispatcherState();
6050 if (state != null) {
6051 state.startTracking(event, this);
6054 } else if (event.getAction() == KeyEvent.ACTION_UP) {
6055 KeyEvent.DispatcherState state = getKeyDispatcherState();
6056 if (state != null) {
6057 state.handleUpEvent(event);
6059 if (event.isTracking() && !event.isCanceled()) {
6060 stopTextActionMode();
6068 public boolean onKeyDown(int keyCode, KeyEvent event) {
6069 final int which = doKeyDown(keyCode, event, null);
6070 if (which == KEY_EVENT_NOT_HANDLED) {
6071 return super.onKeyDown(keyCode, event);
6078 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
6079 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
6080 final int which = doKeyDown(keyCode, down, event);
6081 if (which == KEY_EVENT_NOT_HANDLED) {
6082 // Go through default dispatching.
6083 return super.onKeyMultiple(keyCode, repeatCount, event);
6085 if (which == KEY_EVENT_HANDLED) {
6086 // Consumed the whole thing.
6092 // We are going to dispatch the remaining events to either the input
6093 // or movement method. To do this, we will just send a repeated stream
6094 // of down and up events until we have done the complete repeatCount.
6095 // It would be nice if those interfaces had an onKeyMultiple() method,
6096 // but adding that is a more complicated change.
6097 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
6098 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
6099 // mEditor and mEditor.mInput are not null from doKeyDown
6100 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6101 while (--repeatCount > 0) {
6102 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6103 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
6105 hideErrorIfUnchanged();
6107 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
6108 // mMovement is not null from doKeyDown
6109 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6110 while (--repeatCount > 0) {
6111 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6112 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6120 * Returns true if pressing ENTER in this field advances focus instead
6121 * of inserting the character. This is true mostly in single-line fields,
6122 * but also in mail addresses and subjects which will display on multiple
6123 * lines but where it doesn't make sense to insert newlines.
6125 private boolean shouldAdvanceFocusOnEnter() {
6126 if (getKeyListener() == null) {
6134 if (mEditor != null &&
6135 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6136 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6137 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6138 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
6147 * Returns true if pressing TAB in this field advances focus instead
6148 * of inserting the character. Insert tabs only in multi-line editors.
6150 private boolean shouldAdvanceFocusOnTab() {
6151 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6152 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6153 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6154 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6155 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6162 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6164 return KEY_EVENT_NOT_HANDLED;
6167 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6168 // While this shouldn't be necessary because any time we're preventing default movement we
6169 // should be restricting the focus to remain within this view, thus we'll also receive
6170 // the key up event, occasionally key up events will get dropped and we don't want to
6171 // prevent the user from traversing out of this on the next key down.
6172 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6173 mPreventDefaultMovement = false;
6177 case KeyEvent.KEYCODE_ENTER:
6178 if (event.hasNoModifiers()) {
6179 // When mInputContentType is set, we know that we are
6180 // running in a "modern" cupcake environment, so don't need
6181 // to worry about the application trying to capture
6182 // enter key events.
6183 if (mEditor != null && mEditor.mInputContentType != null) {
6184 // If there is an action listener, given them a
6185 // chance to consume the event.
6186 if (mEditor.mInputContentType.onEditorActionListener != null &&
6187 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6188 this, EditorInfo.IME_NULL, event)) {
6189 mEditor.mInputContentType.enterDown = true;
6190 // We are consuming the enter key for them.
6191 return KEY_EVENT_HANDLED;
6195 // If our editor should move focus when enter is pressed, or
6196 // this is a generated event from an IME action button, then
6197 // don't let it be inserted into the text.
6198 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6199 || shouldAdvanceFocusOnEnter()) {
6200 if (hasOnClickListeners()) {
6201 return KEY_EVENT_NOT_HANDLED;
6203 return KEY_EVENT_HANDLED;
6208 case KeyEvent.KEYCODE_DPAD_CENTER:
6209 if (event.hasNoModifiers()) {
6210 if (shouldAdvanceFocusOnEnter()) {
6211 return KEY_EVENT_NOT_HANDLED;
6216 case KeyEvent.KEYCODE_TAB:
6217 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6218 if (shouldAdvanceFocusOnTab()) {
6219 return KEY_EVENT_NOT_HANDLED;
6224 // Has to be done on key down (and not on key up) to correctly be intercepted.
6225 case KeyEvent.KEYCODE_BACK:
6226 if (mEditor != null && mEditor.mTextActionMode != null) {
6227 stopTextActionMode();
6228 return KEY_EVENT_HANDLED;
6232 case KeyEvent.KEYCODE_CUT:
6233 if (event.hasNoModifiers() && canCut()) {
6234 if (onTextContextMenuItem(ID_CUT)) {
6235 return KEY_EVENT_HANDLED;
6240 case KeyEvent.KEYCODE_COPY:
6241 if (event.hasNoModifiers() && canCopy()) {
6242 if (onTextContextMenuItem(ID_COPY)) {
6243 return KEY_EVENT_HANDLED;
6248 case KeyEvent.KEYCODE_PASTE:
6249 if (event.hasNoModifiers() && canPaste()) {
6250 if (onTextContextMenuItem(ID_PASTE)) {
6251 return KEY_EVENT_HANDLED;
6257 if (mEditor != null && mEditor.mKeyListener != null) {
6258 boolean doDown = true;
6259 if (otherEvent != null) {
6262 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6264 hideErrorIfUnchanged();
6267 return KEY_EVENT_HANDLED;
6269 } catch (AbstractMethodError e) {
6270 // onKeyOther was added after 1.0, so if it isn't
6271 // implemented we need to try to dispatch as a regular down.
6279 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6282 hideErrorIfUnchanged();
6283 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
6287 // bug 650865: sometimes we get a key event before a layout.
6288 // don't try to move around if we don't know the layout.
6290 if (mMovement != null && mLayout != null) {
6291 boolean doDown = true;
6292 if (otherEvent != null) {
6294 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6298 return KEY_EVENT_HANDLED;
6300 } catch (AbstractMethodError e) {
6301 // onKeyOther was added after 1.0, so if it isn't
6302 // implemented we need to try to dispatch as a regular down.
6306 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6307 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6308 mPreventDefaultMovement = true;
6310 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
6315 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6316 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
6320 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6324 public void resetErrorChangedFlag() {
6326 * Keep track of what the error was before doing the input
6327 * so that if an input filter changed the error, we leave
6328 * that error showing. Otherwise, we take down whatever
6329 * error was showing when the user types something.
6331 if (mEditor != null) mEditor.mErrorWasChanged = false;
6337 public void hideErrorIfUnchanged() {
6338 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6339 setError(null, null);
6344 public boolean onKeyUp(int keyCode, KeyEvent event) {
6346 return super.onKeyUp(keyCode, event);
6349 if (!KeyEvent.isModifierKey(keyCode)) {
6350 mPreventDefaultMovement = false;
6354 case KeyEvent.KEYCODE_DPAD_CENTER:
6355 if (event.hasNoModifiers()) {
6357 * If there is a click listener, just call through to
6358 * super, which will invoke it.
6360 * If there isn't a click listener, try to show the soft
6361 * input method. (It will also
6362 * call performClick(), but that won't do anything in
6365 if (!hasOnClickListeners()) {
6366 if (mMovement != null && mText instanceof Editable
6367 && mLayout != null && onCheckIsTextEditor()) {
6368 InputMethodManager imm = InputMethodManager.peekInstance();
6370 if (imm != null && getShowSoftInputOnFocus()) {
6371 imm.showSoftInput(this, 0);
6376 return super.onKeyUp(keyCode, event);
6378 case KeyEvent.KEYCODE_ENTER:
6379 if (event.hasNoModifiers()) {
6380 if (mEditor != null && mEditor.mInputContentType != null
6381 && mEditor.mInputContentType.onEditorActionListener != null
6382 && mEditor.mInputContentType.enterDown) {
6383 mEditor.mInputContentType.enterDown = false;
6384 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6385 this, EditorInfo.IME_NULL, event)) {
6390 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6391 || shouldAdvanceFocusOnEnter()) {
6393 * If there is a click listener, just call through to
6394 * super, which will invoke it.
6396 * If there isn't a click listener, try to advance focus,
6397 * but still call through to super, which will reset the
6398 * pressed state and longpress state. (It will also
6399 * call performClick(), but that won't do anything in
6402 if (!hasOnClickListeners()) {
6403 View v = focusSearch(FOCUS_DOWN);
6406 if (!v.requestFocus(FOCUS_DOWN)) {
6407 throw new IllegalStateException(
6408 "focus search returned a view " +
6409 "that wasn't able to take focus!");
6413 * Return true because we handled the key; super
6414 * will return false because there was no click
6417 super.onKeyUp(keyCode, event);
6419 } else if ((event.getFlags()
6420 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6421 // No target for next focus, but make sure the IME
6422 // if this came from it.
6423 InputMethodManager imm = InputMethodManager.peekInstance();
6424 if (imm != null && imm.isActive(this)) {
6425 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6430 return super.onKeyUp(keyCode, event);
6435 if (mEditor != null && mEditor.mKeyListener != null)
6436 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6439 if (mMovement != null && mLayout != null)
6440 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6443 return super.onKeyUp(keyCode, event);
6447 public boolean onCheckIsTextEditor() {
6448 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6452 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6453 if (onCheckIsTextEditor() && isEnabled()) {
6454 mEditor.createInputMethodStateIfNeeded();
6455 outAttrs.inputType = getInputType();
6456 if (mEditor.mInputContentType != null) {
6457 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6458 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6459 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6460 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6461 outAttrs.extras = mEditor.mInputContentType.extras;
6462 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
6464 outAttrs.imeOptions = EditorInfo.IME_NULL;
6465 outAttrs.hintLocales = null;
6467 if (focusSearch(FOCUS_DOWN) != null) {
6468 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6470 if (focusSearch(FOCUS_UP) != null) {
6471 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6473 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6474 == EditorInfo.IME_ACTION_UNSPECIFIED) {
6475 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6476 // An action has not been set, but the enter key will move to
6477 // the next focus, so set the action to that.
6478 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6480 // An action has not been set, and there is no focus to move
6481 // to, so let's just supply a "done" action.
6482 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6484 if (!shouldAdvanceFocusOnEnter()) {
6485 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6488 if (isMultilineInputType(outAttrs.inputType)) {
6489 // Multi-line text editors should always show an enter key.
6490 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6492 outAttrs.hintText = mHint;
6493 if (mText instanceof Editable) {
6494 InputConnection ic = new EditableInputConnection(this);
6495 outAttrs.initialSelStart = getSelectionStart();
6496 outAttrs.initialSelEnd = getSelectionEnd();
6497 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6505 * If this TextView contains editable content, extract a portion of it
6506 * based on the information in <var>request</var> in to <var>outText</var>.
6507 * @return Returns true if the text was successfully extracted, else false.
6509 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6510 createEditorIfNeeded();
6511 return mEditor.extractText(request, outText);
6515 * This is used to remove all style-impacting spans from text before new
6516 * extracted text is being replaced into it, so that we don't have any
6517 * lingering spans applied during the replace.
6519 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6520 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6521 int i = spans.length;
6524 spannable.removeSpan(spans[i]);
6529 * Apply to this text view the given extracted text, as previously
6530 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6532 public void setExtractedText(ExtractedText text) {
6533 Editable content = getEditableText();
6534 if (text.text != null) {
6535 if (content == null) {
6536 setText(text.text, TextView.BufferType.EDITABLE);
6539 int end = content.length();
6541 if (text.partialStartOffset >= 0) {
6542 final int N = content.length();
6543 start = text.partialStartOffset;
6544 if (start > N) start = N;
6545 end = text.partialEndOffset;
6546 if (end > N) end = N;
6549 removeParcelableSpans(content, start, end);
6550 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6551 if (text.text instanceof Spanned) {
6552 // OK to copy spans only.
6553 TextUtils.copySpansFrom((Spanned) text.text, start, end,
6554 Object.class, content, start);
6557 content.replace(start, end, text.text);
6562 // Now set the selection position... make sure it is in range, to
6563 // avoid crashes. If this is a partial update, it is possible that
6564 // the underlying text may have changed, causing us problems here.
6565 // Also we just don't want to trust clients to do the right thing.
6566 Spannable sp = (Spannable)getText();
6567 final int N = sp.length();
6568 int start = text.selectionStart;
6569 if (start < 0) start = 0;
6570 else if (start > N) start = N;
6571 int end = text.selectionEnd;
6572 if (end < 0) end = 0;
6573 else if (end > N) end = N;
6574 Selection.setSelection(sp, start, end);
6576 // Finally, update the selection mode.
6577 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6578 MetaKeyKeyListener.startSelecting(this, sp);
6580 MetaKeyKeyListener.stopSelecting(this, sp);
6587 public void setExtracting(ExtractedTextRequest req) {
6588 if (mEditor.mInputMethodState != null) {
6589 mEditor.mInputMethodState.mExtractedTextRequest = req;
6591 // This would stop a possible selection mode, but no such mode is started in case
6592 // extracted mode will start. Some text is selected though, and will trigger an action mode
6593 // in the extracted view.
6594 mEditor.hideCursorAndSpanControllers();
6595 stopTextActionMode();
6599 * Called by the framework in response to a text completion from
6600 * the current input method, provided by it calling
6601 * {@link InputConnection#commitCompletion
6602 * InputConnection.commitCompletion()}. The default implementation does
6603 * nothing; text views that are supporting auto-completion should override
6604 * this to do their desired behavior.
6606 * @param text The auto complete text the user has selected.
6608 public void onCommitCompletion(CompletionInfo text) {
6609 // intentionally empty
6613 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6614 * a dictionnary) from the current input method, provided by it calling
6615 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6616 * implementation flashes the background of the corrected word to provide feedback to the user.
6618 * @param info The auto correct info about the text that was corrected.
6620 public void onCommitCorrection(CorrectionInfo info) {
6621 if (mEditor != null) mEditor.onCommitCorrection(info);
6624 public void beginBatchEdit() {
6625 if (mEditor != null) mEditor.beginBatchEdit();
6628 public void endBatchEdit() {
6629 if (mEditor != null) mEditor.endBatchEdit();
6633 * Called by the framework in response to a request to begin a batch
6634 * of edit operations through a call to link {@link #beginBatchEdit()}.
6636 public void onBeginBatchEdit() {
6637 // intentionally empty
6641 * Called by the framework in response to a request to end a batch
6642 * of edit operations through a call to link {@link #endBatchEdit}.
6644 public void onEndBatchEdit() {
6645 // intentionally empty
6649 * Called by the framework in response to a private command from the
6650 * current method, provided by it calling
6651 * {@link InputConnection#performPrivateCommand
6652 * InputConnection.performPrivateCommand()}.
6654 * @param action The action name of the command.
6655 * @param data Any additional data for the command. This may be null.
6656 * @return Return true if you handled the command, else false.
6658 public boolean onPrivateIMECommand(String action, Bundle data) {
6662 private void nullLayouts() {
6663 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6664 mSavedLayout = (BoringLayout) mLayout;
6666 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6667 mSavedHintLayout = (BoringLayout) mHintLayout;
6670 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6672 mBoring = mHintBoring = null;
6674 // Since it depends on the value of mLayout
6675 if (mEditor != null) mEditor.prepareCursorControllers();
6679 * Make a new Layout based on the already-measured size of the view,
6680 * on the assumption that it was measured correctly at some point.
6682 private void assumeLayout() {
6683 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6689 int physicalWidth = width;
6691 if (mHorizontallyScrolling) {
6695 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6696 physicalWidth, false);
6699 private Layout.Alignment getLayoutAlignment() {
6700 Layout.Alignment alignment;
6701 switch (getTextAlignment()) {
6702 case TEXT_ALIGNMENT_GRAVITY:
6703 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6705 alignment = Layout.Alignment.ALIGN_NORMAL;
6708 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6711 alignment = Layout.Alignment.ALIGN_LEFT;
6714 alignment = Layout.Alignment.ALIGN_RIGHT;
6716 case Gravity.CENTER_HORIZONTAL:
6717 alignment = Layout.Alignment.ALIGN_CENTER;
6720 alignment = Layout.Alignment.ALIGN_NORMAL;
6724 case TEXT_ALIGNMENT_TEXT_START:
6725 alignment = Layout.Alignment.ALIGN_NORMAL;
6727 case TEXT_ALIGNMENT_TEXT_END:
6728 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6730 case TEXT_ALIGNMENT_CENTER:
6731 alignment = Layout.Alignment.ALIGN_CENTER;
6733 case TEXT_ALIGNMENT_VIEW_START:
6734 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6735 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6737 case TEXT_ALIGNMENT_VIEW_END:
6738 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6739 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6741 case TEXT_ALIGNMENT_INHERIT:
6742 // This should never happen as we have already resolved the text alignment
6743 // but better safe than sorry so we just fall through
6745 alignment = Layout.Alignment.ALIGN_NORMAL;
6752 * The width passed in is now the desired layout width,
6753 * not the full view width with padding.
6756 protected void makeNewLayout(int wantWidth, int hintWidth,
6757 BoringLayout.Metrics boring,
6758 BoringLayout.Metrics hintBoring,
6759 int ellipsisWidth, boolean bringIntoView) {
6762 // Update "old" cached values
6763 mOldMaximum = mMaximum;
6764 mOldMaxMode = mMaxMode;
6766 mHighlightPathBogus = true;
6768 if (wantWidth < 0) {
6771 if (hintWidth < 0) {
6775 Layout.Alignment alignment = getLayoutAlignment();
6776 final boolean testDirChange = mSingleLine && mLayout != null &&
6777 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6778 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6780 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6781 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6782 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6783 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6784 TruncateAt effectiveEllipsize = mEllipsize;
6785 if (mEllipsize == TruncateAt.MARQUEE &&
6786 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6787 effectiveEllipsize = TruncateAt.END_SMALL;
6790 if (mTextDir == null) {
6791 mTextDir = getTextDirectionHeuristic();
6794 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6795 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6796 if (switchEllipsize) {
6797 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6798 TruncateAt.END : TruncateAt.MARQUEE;
6799 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6800 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6803 shouldEllipsize = mEllipsize != null;
6806 if (mHint != null) {
6807 if (shouldEllipsize) hintWidth = wantWidth;
6809 if (hintBoring == UNKNOWN_BORING) {
6810 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6812 if (hintBoring != null) {
6813 mHintBoring = hintBoring;
6817 if (hintBoring != null) {
6818 if (hintBoring.width <= hintWidth &&
6819 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6820 if (mSavedHintLayout != null) {
6821 mHintLayout = mSavedHintLayout.
6822 replaceOrMake(mHint, mTextPaint,
6823 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6824 hintBoring, mIncludePad);
6826 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6827 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6828 hintBoring, mIncludePad);
6831 mSavedHintLayout = (BoringLayout) mHintLayout;
6832 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6833 if (mSavedHintLayout != null) {
6834 mHintLayout = mSavedHintLayout.
6835 replaceOrMake(mHint, mTextPaint,
6836 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6837 hintBoring, mIncludePad, mEllipsize,
6840 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6841 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6842 hintBoring, mIncludePad, mEllipsize,
6847 // TODO: code duplication with makeSingleLayout()
6848 if (mHintLayout == null) {
6849 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6850 mHint.length(), mTextPaint, hintWidth)
6851 .setAlignment(alignment)
6852 .setTextDirection(mTextDir)
6853 .setLineSpacing(mSpacingAdd, mSpacingMult)
6854 .setIncludePad(mIncludePad)
6855 .setBreakStrategy(mBreakStrategy)
6856 .setHyphenationFrequency(mHyphenationFrequency)
6857 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6858 if (shouldEllipsize) {
6859 builder.setEllipsize(mEllipsize)
6860 .setEllipsizedWidth(ellipsisWidth);
6862 mHintLayout = builder.build();
6866 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6867 registerForPreDraw();
6870 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6871 if (!compressText(ellipsisWidth)) {
6872 final int height = mLayoutParams.height;
6873 // If the size of the view does not depend on the size of the text, try to
6874 // start the marquee immediately
6875 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6878 // Defer the start of the marquee until we know our width (see setFrame())
6879 mRestartMarquee = true;
6884 // CursorControllers need a non-null mLayout
6885 if (mEditor != null) mEditor.prepareCursorControllers();
6891 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6892 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6894 Layout result = null;
6895 if (mText instanceof Spannable) {
6896 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6897 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6898 mBreakStrategy, mHyphenationFrequency,
6899 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6901 if (boring == UNKNOWN_BORING) {
6902 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6903 if (boring != null) {
6908 if (boring != null) {
6909 if (boring.width <= wantWidth &&
6910 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6911 if (useSaved && mSavedLayout != null) {
6912 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6913 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6914 boring, mIncludePad);
6916 result = BoringLayout.make(mTransformed, mTextPaint,
6917 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6918 boring, mIncludePad);
6922 mSavedLayout = (BoringLayout) result;
6924 } else if (shouldEllipsize && boring.width <= wantWidth) {
6925 if (useSaved && mSavedLayout != null) {
6926 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6927 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6928 boring, mIncludePad, effectiveEllipsize,
6931 result = BoringLayout.make(mTransformed, mTextPaint,
6932 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6933 boring, mIncludePad, effectiveEllipsize,
6939 if (result == null) {
6940 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6941 0, mTransformed.length(), mTextPaint, wantWidth)
6942 .setAlignment(alignment)
6943 .setTextDirection(mTextDir)
6944 .setLineSpacing(mSpacingAdd, mSpacingMult)
6945 .setIncludePad(mIncludePad)
6946 .setBreakStrategy(mBreakStrategy)
6947 .setHyphenationFrequency(mHyphenationFrequency)
6948 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6949 if (shouldEllipsize) {
6950 builder.setEllipsize(effectiveEllipsize)
6951 .setEllipsizedWidth(ellipsisWidth);
6954 // TODO: explore always setting maxLines
6955 result = builder.build();
6960 private boolean compressText(float width) {
6961 if (isHardwareAccelerated()) return false;
6963 // Only compress the text if it hasn't been compressed by the previous pass
6964 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6965 mTextPaint.getTextScaleX() == 1.0f) {
6966 final float textWidth = mLayout.getLineWidth(0);
6967 final float overflow = (textWidth + 1.0f - width) / width;
6968 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6969 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6970 post(new Runnable() {
6982 private static int desired(Layout layout) {
6983 int n = layout.getLineCount();
6984 CharSequence text = layout.getText();
6987 // if any line was wrapped, we can't use it.
6988 // but it's ok for the last line not to have a newline
6990 for (int i = 0; i < n - 1; i++) {
6991 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6995 for (int i = 0; i < n; i++) {
6996 max = Math.max(max, layout.getLineWidth(i));
6999 return (int) Math.ceil(max);
7003 * Set whether the TextView includes extra top and bottom padding to make
7004 * room for accents that go above the normal ascent and descent.
7005 * The default is true.
7007 * @see #getIncludeFontPadding()
7009 * @attr ref android.R.styleable#TextView_includeFontPadding
7011 public void setIncludeFontPadding(boolean includepad) {
7012 if (mIncludePad != includepad) {
7013 mIncludePad = includepad;
7015 if (mLayout != null) {
7024 * Gets whether the TextView includes extra top and bottom padding to make
7025 * room for accents that go above the normal ascent and descent.
7027 * @see #setIncludeFontPadding(boolean)
7029 * @attr ref android.R.styleable#TextView_includeFontPadding
7031 public boolean getIncludeFontPadding() {
7035 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
7038 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7039 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7040 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7041 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7042 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7047 BoringLayout.Metrics boring = UNKNOWN_BORING;
7048 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7050 if (mTextDir == null) {
7051 mTextDir = getTextDirectionHeuristic();
7055 boolean fromexisting = false;
7057 if (widthMode == MeasureSpec.EXACTLY) {
7058 // Parent has told us how big to be. So be it.
7061 if (mLayout != null && mEllipsize == null) {
7062 des = desired(mLayout);
7066 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
7067 if (boring != null) {
7071 fromexisting = true;
7074 if (boring == null || boring == UNKNOWN_BORING) {
7076 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
7080 width = boring.width;
7083 final Drawables dr = mDrawables;
7085 width = Math.max(width, dr.mDrawableWidthTop);
7086 width = Math.max(width, dr.mDrawableWidthBottom);
7089 if (mHint != null) {
7093 if (mHintLayout != null && mEllipsize == null) {
7094 hintDes = desired(mHintLayout);
7098 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
7099 if (hintBoring != null) {
7100 mHintBoring = hintBoring;
7104 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7106 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
7108 hintWidth = hintDes;
7110 hintWidth = hintBoring.width;
7113 if (hintWidth > width) {
7118 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7120 if (mMaxWidthMode == EMS) {
7121 width = Math.min(width, mMaxWidth * getLineHeight());
7123 width = Math.min(width, mMaxWidth);
7126 if (mMinWidthMode == EMS) {
7127 width = Math.max(width, mMinWidth * getLineHeight());
7129 width = Math.max(width, mMinWidth);
7132 // Check against our minimum width
7133 width = Math.max(width, getSuggestedMinimumWidth());
7135 if (widthMode == MeasureSpec.AT_MOST) {
7136 width = Math.min(widthSize, width);
7140 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7141 int unpaddedWidth = want;
7143 if (mHorizontallyScrolling) want = VERY_WIDE;
7145 int hintWant = want;
7146 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
7148 if (mLayout == null) {
7149 makeNewLayout(want, hintWant, boring, hintBoring,
7150 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7152 final boolean layoutChanged = (mLayout.getWidth() != want) ||
7153 (hintWidth != hintWant) ||
7154 (mLayout.getEllipsizedWidth() !=
7155 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7157 final boolean widthChanged = (mHint == null) &&
7158 (mEllipsize == null) &&
7159 (want > mLayout.getWidth()) &&
7160 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7162 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7164 if (layoutChanged || maximumChanged) {
7165 if (!maximumChanged && widthChanged) {
7166 mLayout.increaseWidthTo(want);
7168 makeNewLayout(want, hintWant, boring, hintBoring,
7169 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7172 // Nothing has changed
7176 if (heightMode == MeasureSpec.EXACTLY) {
7177 // Parent has told us how big to be. So be it.
7178 height = heightSize;
7179 mDesiredHeightAtMeasure = -1;
7181 int desired = getDesiredHeight();
7184 mDesiredHeightAtMeasure = desired;
7186 if (heightMode == MeasureSpec.AT_MOST) {
7187 height = Math.min(desired, heightSize);
7191 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
7192 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
7193 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
7197 * We didn't let makeNewLayout() register to bring the cursor into view,
7198 * so do it here if there is any possibility that it is needed.
7200 if (mMovement != null ||
7201 mLayout.getWidth() > unpaddedWidth ||
7202 mLayout.getHeight() > unpaddedHeight) {
7203 registerForPreDraw();
7208 setMeasuredDimension(width, height);
7211 private int getDesiredHeight() {
7213 getDesiredHeight(mLayout, true),
7214 getDesiredHeight(mHintLayout, mEllipsize != null));
7217 private int getDesiredHeight(Layout layout, boolean cap) {
7218 if (layout == null) {
7222 int linecount = layout.getLineCount();
7223 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7224 int desired = layout.getLineTop(linecount);
7226 final Drawables dr = mDrawables;
7228 desired = Math.max(desired, dr.mDrawableHeightLeft);
7229 desired = Math.max(desired, dr.mDrawableHeightRight);
7234 if (mMaxMode == LINES) {
7236 * Don't cap the hint to a certain number of lines.
7237 * (Do cap it, though, if we have a maximum pixel height.)
7240 if (linecount > mMaximum) {
7241 desired = layout.getLineTop(mMaximum);
7244 desired = Math.max(desired, dr.mDrawableHeightLeft);
7245 desired = Math.max(desired, dr.mDrawableHeightRight);
7249 linecount = mMaximum;
7253 desired = Math.min(desired, mMaximum);
7256 if (mMinMode == LINES) {
7257 if (linecount < mMinimum) {
7258 desired += getLineHeight() * (mMinimum - linecount);
7261 desired = Math.max(desired, mMinimum);
7264 // Check against our minimum height
7265 desired = Math.max(desired, getSuggestedMinimumHeight());
7271 * Check whether a change to the existing text layout requires a
7274 private void checkForResize() {
7275 boolean sizeChanged = false;
7277 if (mLayout != null) {
7278 // Check if our width changed
7279 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7284 // Check if our height changed
7285 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7286 int desiredHeight = getDesiredHeight();
7288 if (desiredHeight != this.getHeight()) {
7291 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7292 if (mDesiredHeightAtMeasure >= 0) {
7293 int desiredHeight = getDesiredHeight();
7295 if (desiredHeight != mDesiredHeightAtMeasure) {
7304 // caller will have already invalidated
7309 * Check whether entirely new text requires a new view layout
7310 * or merely a new text layout.
7312 private void checkForRelayout() {
7313 // If we have a fixed width, we can just swap in a new text layout
7314 // if the text height stays the same or if the view height is fixed.
7316 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7317 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7318 (mHint == null || mHintLayout != null) &&
7319 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7320 // Static width, so try making a new text layout.
7322 int oldht = mLayout.getHeight();
7323 int want = mLayout.getWidth();
7324 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7327 * No need to bring the text into view, since the size is not
7328 * changing (unless we do the requestLayout(), in which case it
7329 * will happen at measure).
7331 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7332 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7335 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7336 // In a fixed-height view, so use our new text layout.
7337 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7338 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7343 // Dynamic height, but height has stayed the same,
7344 // so use our new text layout.
7345 if (mLayout.getHeight() == oldht &&
7346 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7352 // We lose: the height has changed and we have a dynamic height.
7353 // Request a new view layout using our new text layout.
7357 // Dynamic width, so we have no choice but to request a new
7358 // view layout with a new text layout.
7366 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7367 super.onLayout(changed, left, top, right, bottom);
7368 if (mDeferScroll >= 0) {
7369 int curs = mDeferScroll;
7371 bringPointIntoView(Math.min(curs, mText.length()));
7375 private boolean isShowingHint() {
7376 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7380 * Returns true if anything changed.
7382 private boolean bringTextIntoView() {
7383 Layout layout = isShowingHint() ? mHintLayout : mLayout;
7385 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7386 line = layout.getLineCount() - 1;
7389 Layout.Alignment a = layout.getParagraphAlignment(line);
7390 int dir = layout.getParagraphDirection(line);
7391 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7392 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7393 int ht = layout.getHeight();
7395 int scrollx, scrolly;
7397 // Convert to left, center, or right alignment.
7398 if (a == Layout.Alignment.ALIGN_NORMAL) {
7399 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7400 Layout.Alignment.ALIGN_RIGHT;
7401 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7402 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7403 Layout.Alignment.ALIGN_LEFT;
7406 if (a == Layout.Alignment.ALIGN_CENTER) {
7408 * Keep centered if possible, or, if it is too wide to fit,
7409 * keep leading edge in view.
7412 int left = (int) Math.floor(layout.getLineLeft(line));
7413 int right = (int) Math.ceil(layout.getLineRight(line));
7415 if (right - left < hspace) {
7416 scrollx = (right + left) / 2 - hspace / 2;
7419 scrollx = right - hspace;
7424 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7425 int right = (int) Math.ceil(layout.getLineRight(line));
7426 scrollx = right - hspace;
7427 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7428 scrollx = (int) Math.floor(layout.getLineLeft(line));
7434 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7435 scrolly = ht - vspace;
7441 if (scrollx != mScrollX || scrolly != mScrollY) {
7442 scrollTo(scrollx, scrolly);
7450 * Move the point, specified by the offset, into the view if it is needed.
7451 * This has to be called after layout. Returns true if anything changed.
7453 public boolean bringPointIntoView(int offset) {
7454 if (isLayoutRequested()) {
7455 mDeferScroll = offset;
7458 boolean changed = false;
7460 Layout layout = isShowingHint() ? mHintLayout: mLayout;
7462 if (layout == null) return changed;
7464 int line = layout.getLineForOffset(offset);
7468 switch (layout.getParagraphAlignment(line)) {
7476 grav = layout.getParagraphDirection(line);
7478 case ALIGN_OPPOSITE:
7479 grav = -layout.getParagraphDirection(line);
7487 // We only want to clamp the cursor to fit within the layout width
7488 // in left-to-right modes, because in a right to left alignment,
7489 // we want to scroll to keep the line-right on the screen, as other
7490 // lines are likely to have text flush with the right margin, which
7491 // we want to keep visible.
7492 // A better long-term solution would probably be to measure both
7493 // the full line and a blank-trimmed version, and, for example, use
7494 // the latter measurement for centering and right alignment, but for
7495 // the time being we only implement the cursor clamping in left to
7496 // right where it is most likely to be annoying.
7497 final boolean clamped = grav > 0;
7498 // FIXME: Is it okay to truncate this, or should we round?
7499 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7500 final int top = layout.getLineTop(line);
7501 final int bottom = layout.getLineTop(line + 1);
7503 int left = (int) Math.floor(layout.getLineLeft(line));
7504 int right = (int) Math.ceil(layout.getLineRight(line));
7505 int ht = layout.getHeight();
7507 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7508 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7509 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7510 // If cursor has been clamped, make sure we don't scroll.
7511 right = Math.max(x, left + hspace);
7514 int hslack = (bottom - top) / 2;
7515 int vslack = hslack;
7517 if (vslack > vspace / 4)
7518 vslack = vspace / 4;
7519 if (hslack > hspace / 4)
7520 hslack = hspace / 4;
7525 if (top - vs < vslack)
7527 if (bottom - vs > vspace - vslack)
7528 vs = bottom - (vspace - vslack);
7529 if (ht - vs < vspace)
7535 if (x - hs < hslack) {
7538 if (x - hs > hspace - hslack) {
7539 hs = x - (hspace - hslack);
7546 if (right - hs < hspace)
7547 hs = right - hspace;
7548 } else if (grav > 0) {
7549 if (right - hs < hspace)
7550 hs = right - hspace;
7553 } else /* grav == 0 */ {
7554 if (right - left <= hspace) {
7556 * If the entire text fits, center it exactly.
7558 hs = left - (hspace - (right - left)) / 2;
7559 } else if (x > right - hslack) {
7561 * If we are near the right edge, keep the right edge
7562 * at the edge of the view.
7564 hs = right - hspace;
7565 } else if (x < left + hslack) {
7567 * If we are near the left edge, keep the left edge
7568 * at the edge of the view.
7571 } else if (left > hs) {
7573 * Is there whitespace visible at the left? Fix it if so.
7576 } else if (right < hs + hspace) {
7578 * Is there whitespace visible at the right? Fix it if so.
7580 hs = right - hspace;
7583 * Otherwise, float as needed.
7585 if (x - hs < hslack) {
7588 if (x - hs > hspace - hslack) {
7589 hs = x - (hspace - hslack);
7594 if (hs != mScrollX || vs != mScrollY) {
7595 if (mScroller == null) {
7598 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7599 int dx = hs - mScrollX;
7600 int dy = vs - mScrollY;
7602 if (duration > ANIMATED_SCROLL_GAP) {
7603 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7604 awakenScrollBars(mScroller.getDuration());
7607 if (!mScroller.isFinished()) {
7608 mScroller.abortAnimation();
7614 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7621 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7622 // requestRectangleOnScreen() is in terms of content coordinates.
7624 // The offsets here are to ensure the rectangle we are using is
7625 // within our view bounds, in case the cursor is on the far left
7626 // or right. If it isn't withing the bounds, then this request
7628 if (mTempRect == null) mTempRect = new Rect();
7629 mTempRect.set(x - 2, top, x + 2, bottom);
7630 getInterestingRect(mTempRect, line);
7631 mTempRect.offset(mScrollX, mScrollY);
7633 if (requestRectangleOnScreen(mTempRect)) {
7642 * Move the cursor, if needed, so that it is at an offset that is visible
7643 * to the user. This will not move the cursor if it represents more than
7644 * one character (a selection range). This will only work if the
7645 * TextView contains spannable text; otherwise it will do nothing.
7647 * @return True if the cursor was actually moved, false otherwise.
7649 public boolean moveCursorToVisibleOffset() {
7650 if (!(mText instanceof Spannable)) {
7653 int start = getSelectionStart();
7654 int end = getSelectionEnd();
7659 // First: make sure the line is visible on screen:
7661 int line = mLayout.getLineForOffset(start);
7663 final int top = mLayout.getLineTop(line);
7664 final int bottom = mLayout.getLineTop(line + 1);
7665 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7666 int vslack = (bottom - top) / 2;
7667 if (vslack > vspace / 4)
7668 vslack = vspace / 4;
7669 final int vs = mScrollY;
7671 if (top < (vs+vslack)) {
7672 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7673 } else if (bottom > (vspace+vs-vslack)) {
7674 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7677 // Next: make sure the character is visible on screen:
7679 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7680 final int hs = mScrollX;
7681 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7682 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7684 // line might contain bidirectional text
7685 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7686 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7688 int newStart = start;
7689 if (newStart < lowChar) {
7691 } else if (newStart > highChar) {
7692 newStart = highChar;
7695 if (newStart != start) {
7696 Selection.setSelection((Spannable)mText, newStart);
7704 public void computeScroll() {
7705 if (mScroller != null) {
7706 if (mScroller.computeScrollOffset()) {
7707 mScrollX = mScroller.getCurrX();
7708 mScrollY = mScroller.getCurrY();
7709 invalidateParentCaches();
7710 postInvalidate(); // So we draw again
7715 private void getInterestingRect(Rect r, int line) {
7716 convertFromViewportToContentCoordinates(r);
7718 // Rectangle can can be expanded on first and last line to take
7719 // padding into account.
7720 // TODO Take left/right padding into account too?
7721 if (line == 0) r.top -= getExtendedPaddingTop();
7722 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7725 private void convertFromViewportToContentCoordinates(Rect r) {
7726 final int horizontalOffset = viewportToContentHorizontalOffset();
7727 r.left += horizontalOffset;
7728 r.right += horizontalOffset;
7730 final int verticalOffset = viewportToContentVerticalOffset();
7731 r.top += verticalOffset;
7732 r.bottom += verticalOffset;
7735 int viewportToContentHorizontalOffset() {
7736 return getCompoundPaddingLeft() - mScrollX;
7739 int viewportToContentVerticalOffset() {
7740 int offset = getExtendedPaddingTop() - mScrollY;
7741 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7742 offset += getVerticalOffset(false);
7748 public void debug(int depth) {
7751 String output = debugIndent(depth);
7752 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7753 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7756 if (mText != null) {
7758 output += "mText=\"" + mText + "\" ";
7759 if (mLayout != null) {
7760 output += "mLayout width=" + mLayout.getWidth()
7761 + " height=" + mLayout.getHeight();
7764 output += "mText=NULL";
7766 Log.d(VIEW_LOG_TAG, output);
7770 * Convenience for {@link Selection#getSelectionStart}.
7772 @ViewDebug.ExportedProperty(category = "text")
7773 public int getSelectionStart() {
7774 return Selection.getSelectionStart(getText());
7778 * Convenience for {@link Selection#getSelectionEnd}.
7780 @ViewDebug.ExportedProperty(category = "text")
7781 public int getSelectionEnd() {
7782 return Selection.getSelectionEnd(getText());
7786 * Return true iff there is a selection inside this text view.
7788 public boolean hasSelection() {
7789 final int selectionStart = getSelectionStart();
7790 final int selectionEnd = getSelectionEnd();
7792 return selectionStart >= 0 && selectionStart != selectionEnd;
7795 String getSelectedText() {
7796 if (!hasSelection()) {
7800 final int start = getSelectionStart();
7801 final int end = getSelectionEnd();
7802 return String.valueOf(
7803 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
7807 * Sets the properties of this field (lines, horizontally scrolling,
7808 * transformation method) to be for a single-line input.
7810 * @attr ref android.R.styleable#TextView_singleLine
7812 public void setSingleLine() {
7813 setSingleLine(true);
7817 * Sets the properties of this field to transform input to ALL CAPS
7818 * display. This may use a "small caps" formatting if available.
7819 * This setting will be ignored if this field is editable or selectable.
7821 * This call replaces the current transformation method. Disabling this
7822 * will not necessarily restore the previous behavior from before this
7825 * @see #setTransformationMethod(TransformationMethod)
7826 * @attr ref android.R.styleable#TextView_textAllCaps
7828 public void setAllCaps(boolean allCaps) {
7830 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7832 setTransformationMethod(null);
7837 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7838 * transformation method) to be for a single-line input; if false, restores these to the default
7841 * Note that the default conditions are not necessarily those that were in effect prior this
7842 * method, and you may want to reset these properties to your custom values.
7844 * @attr ref android.R.styleable#TextView_singleLine
7846 @android.view.RemotableViewMethod
7847 public void setSingleLine(boolean singleLine) {
7848 // Could be used, but may break backward compatibility.
7849 // if (mSingleLine == singleLine) return;
7850 setInputTypeSingleLine(singleLine);
7851 applySingleLine(singleLine, true, true);
7855 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7858 private void setInputTypeSingleLine(boolean singleLine) {
7859 if (mEditor != null &&
7860 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7862 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7864 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7869 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7870 boolean changeMaxLines) {
7871 mSingleLine = singleLine;
7874 setHorizontallyScrolling(true);
7875 if (applyTransformation) {
7876 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7879 if (changeMaxLines) {
7880 setMaxLines(Integer.MAX_VALUE);
7882 setHorizontallyScrolling(false);
7883 if (applyTransformation) {
7884 setTransformationMethod(null);
7890 * Causes words in the text that are longer than the view is wide
7891 * to be ellipsized instead of broken in the middle. You may also
7892 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7893 * to constrain the text to a single line. Use <code>null</code>
7894 * to turn off ellipsizing.
7896 * If {@link #setMaxLines} has been used to set two or more lines,
7897 * only {@link android.text.TextUtils.TruncateAt#END} and
7898 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7899 * (other ellipsizing types will not do anything).
7901 * @attr ref android.R.styleable#TextView_ellipsize
7903 public void setEllipsize(TextUtils.TruncateAt where) {
7904 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7905 if (mEllipsize != where) {
7908 if (mLayout != null) {
7917 * Sets how many times to repeat the marquee animation. Only applied if the
7918 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7920 * @see #getMarqueeRepeatLimit()
7922 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7924 public void setMarqueeRepeatLimit(int marqueeLimit) {
7925 mMarqueeRepeatLimit = marqueeLimit;
7929 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7930 * TextView has marquee enabled.
7932 * @return the number of times the marquee animation is repeated. -1 if the animation
7933 * repeats indefinitely
7935 * @see #setMarqueeRepeatLimit(int)
7937 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7939 public int getMarqueeRepeatLimit() {
7940 return mMarqueeRepeatLimit;
7944 * Returns where, if anywhere, words that are longer than the view
7945 * is wide should be ellipsized.
7947 @ViewDebug.ExportedProperty
7948 public TextUtils.TruncateAt getEllipsize() {
7953 * Set the TextView so that when it takes focus, all the text is
7956 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7958 @android.view.RemotableViewMethod
7959 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7960 createEditorIfNeeded();
7961 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7963 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7964 setText(mText, BufferType.SPANNABLE);
7969 * Set whether the cursor is visible. The default is true. Note that this property only
7970 * makes sense for editable TextView.
7972 * @see #isCursorVisible()
7974 * @attr ref android.R.styleable#TextView_cursorVisible
7976 @android.view.RemotableViewMethod
7977 public void setCursorVisible(boolean visible) {
7978 if (visible && mEditor == null) return; // visible is the default value with no edit data
7979 createEditorIfNeeded();
7980 if (mEditor.mCursorVisible != visible) {
7981 mEditor.mCursorVisible = visible;
7984 mEditor.makeBlink();
7986 // InsertionPointCursorController depends on mCursorVisible
7987 mEditor.prepareCursorControllers();
7992 * @return whether or not the cursor is visible (assuming this TextView is editable)
7994 * @see #setCursorVisible(boolean)
7996 * @attr ref android.R.styleable#TextView_cursorVisible
7998 public boolean isCursorVisible() {
7999 // true is the default value
8000 return mEditor == null ? true : mEditor.mCursorVisible;
8003 private boolean canMarquee() {
8004 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
8005 return width > 0 && (mLayout.getLineWidth(0) > width ||
8006 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
8007 mSavedMarqueeModeLayout.getLineWidth(0) > width));
8010 private void startMarquee() {
8011 // Do not ellipsize EditText
8012 if (getKeyListener() != null) return;
8014 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8018 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
8019 getLineCount() == 1 && canMarquee()) {
8021 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8022 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8023 final Layout tmp = mLayout;
8024 mLayout = mSavedMarqueeModeLayout;
8025 mSavedMarqueeModeLayout = tmp;
8026 setHorizontalFadingEdgeEnabled(true);
8031 if (mMarquee == null) mMarquee = new Marquee(this);
8032 mMarquee.start(mMarqueeRepeatLimit);
8036 private void stopMarquee() {
8037 if (mMarquee != null && !mMarquee.isStopped()) {
8041 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8042 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8043 final Layout tmp = mSavedMarqueeModeLayout;
8044 mSavedMarqueeModeLayout = mLayout;
8046 setHorizontalFadingEdgeEnabled(false);
8052 private void startStopMarquee(boolean start) {
8053 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8063 * This method is called when the text is changed, in case any subclasses
8064 * would like to know.
8066 * Within <code>text</code>, the <code>lengthAfter</code> characters
8067 * beginning at <code>start</code> have just replaced old text that had
8068 * length <code>lengthBefore</code>. It is an error to attempt to make
8069 * changes to <code>text</code> from this callback.
8071 * @param text The text the TextView is displaying
8072 * @param start The offset of the start of the range of the text that was
8074 * @param lengthBefore The length of the former text that has been replaced
8075 * @param lengthAfter The length of the replacement modified text
8077 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
8078 // intentionally empty, template pattern method can be overridden by subclasses
8082 * This method is called when the selection has changed, in case any
8083 * subclasses would like to know.
8085 * @param selStart The new selection start location.
8086 * @param selEnd The new selection end location.
8088 protected void onSelectionChanged(int selStart, int selEnd) {
8089 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
8093 * Adds a TextWatcher to the list of those whose methods are called
8094 * whenever this TextView's text changes.
8096 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8097 * not called after {@link #setText} calls. Now, doing {@link #setText}
8098 * if there are any text changed listeners forces the buffer type to
8099 * Editable if it would not otherwise be and does call this method.
8101 public void addTextChangedListener(TextWatcher watcher) {
8102 if (mListeners == null) {
8103 mListeners = new ArrayList<TextWatcher>();
8106 mListeners.add(watcher);
8110 * Removes the specified TextWatcher from the list of those whose
8111 * methods are called
8112 * whenever this TextView's text changes.
8114 public void removeTextChangedListener(TextWatcher watcher) {
8115 if (mListeners != null) {
8116 int i = mListeners.indexOf(watcher);
8119 mListeners.remove(i);
8124 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
8125 if (mListeners != null) {
8126 final ArrayList<TextWatcher> list = mListeners;
8127 final int count = list.size();
8128 for (int i = 0; i < count; i++) {
8129 list.get(i).beforeTextChanged(text, start, before, after);
8133 // The spans that are inside or intersect the modified region no longer make sense
8134 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8135 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
8138 // Removes all spans that are inside or actually overlap the start..end range
8139 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
8140 if (!(mText instanceof Editable)) return;
8141 Editable text = (Editable) mText;
8143 T[] spans = text.getSpans(start, end, type);
8144 final int length = spans.length;
8145 for (int i = 0; i < length; i++) {
8146 final int spanStart = text.getSpanStart(spans[i]);
8147 final int spanEnd = text.getSpanEnd(spans[i]);
8148 if (spanEnd == start || spanStart == end) break;
8149 text.removeSpan(spans[i]);
8153 void removeAdjacentSuggestionSpans(final int pos) {
8154 if (!(mText instanceof Editable)) return;
8155 final Editable text = (Editable) mText;
8157 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8158 final int length = spans.length;
8159 for (int i = 0; i < length; i++) {
8160 final int spanStart = text.getSpanStart(spans[i]);
8161 final int spanEnd = text.getSpanEnd(spans[i]);
8162 if (spanEnd == pos || spanStart == pos) {
8163 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8164 text.removeSpan(spans[i]);
8171 * Not private so it can be called from an inner class without going
8174 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
8175 if (mListeners != null) {
8176 final ArrayList<TextWatcher> list = mListeners;
8177 final int count = list.size();
8178 for (int i = 0; i < count; i++) {
8179 list.get(i).onTextChanged(text, start, before, after);
8183 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
8187 * Not private so it can be called from an inner class without going
8190 void sendAfterTextChanged(Editable text) {
8191 if (mListeners != null) {
8192 final ArrayList<TextWatcher> list = mListeners;
8193 final int count = list.size();
8194 for (int i = 0; i < count; i++) {
8195 list.get(i).afterTextChanged(text);
8198 hideErrorIfUnchanged();
8201 void updateAfterEdit() {
8203 int curs = getSelectionStart();
8205 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8206 registerForPreDraw();
8212 mHighlightPathBogus = true;
8213 if (mEditor != null) mEditor.makeBlink();
8214 bringPointIntoView(curs);
8219 * Not private so it can be called from an inner class without going
8222 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8223 sLastCutCopyOrTextChangedTime = 0;
8225 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8226 if (ims == null || ims.mBatchEditNesting == 0) {
8230 ims.mContentChanged = true;
8231 if (ims.mChangedStart < 0) {
8232 ims.mChangedStart = start;
8233 ims.mChangedEnd = start+before;
8235 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8236 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8238 ims.mChangedDelta += after-before;
8240 resetErrorChangedFlag();
8241 sendOnTextChanged(buffer, start, before, after);
8242 onTextChanged(buffer, start, before, after);
8246 * Not private so it can be called from an inner class without going
8249 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8250 // XXX Make the start and end move together if this ends up
8251 // spending too much time invalidating.
8253 boolean selChanged = false;
8254 int newSelStart=-1, newSelEnd=-1;
8256 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8258 if (what == Selection.SELECTION_END) {
8260 newSelEnd = newStart;
8262 if (oldStart >= 0 || newStart >= 0) {
8263 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8265 registerForPreDraw();
8266 if (mEditor != null) mEditor.makeBlink();
8270 if (what == Selection.SELECTION_START) {
8272 newSelStart = newStart;
8274 if (oldStart >= 0 || newStart >= 0) {
8275 int end = Selection.getSelectionEnd(buf);
8276 invalidateCursor(end, oldStart, newStart);
8281 mHighlightPathBogus = true;
8282 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8284 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8285 if (newSelStart < 0) {
8286 newSelStart = Selection.getSelectionStart(buf);
8288 if (newSelEnd < 0) {
8289 newSelEnd = Selection.getSelectionEnd(buf);
8291 onSelectionChanged(newSelStart, newSelEnd);
8295 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8296 what instanceof CharacterStyle) {
8297 if (ims == null || ims.mBatchEditNesting == 0) {
8299 mHighlightPathBogus = true;
8302 ims.mContentChanged = true;
8304 if (mEditor != null) {
8305 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8306 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8310 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8311 mHighlightPathBogus = true;
8312 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8313 ims.mSelectionModeChanged = true;
8316 if (Selection.getSelectionStart(buf) >= 0) {
8317 if (ims == null || ims.mBatchEditNesting == 0) {
8320 ims.mCursorChanged = true;
8325 if (what instanceof ParcelableSpan) {
8326 // If this is a span that can be sent to a remote process,
8327 // the current extract editor would be interested in it.
8328 if (ims != null && ims.mExtractedTextRequest != null) {
8329 if (ims.mBatchEditNesting != 0) {
8330 if (oldStart >= 0) {
8331 if (ims.mChangedStart > oldStart) {
8332 ims.mChangedStart = oldStart;
8334 if (ims.mChangedStart > oldEnd) {
8335 ims.mChangedStart = oldEnd;
8338 if (newStart >= 0) {
8339 if (ims.mChangedStart > newStart) {
8340 ims.mChangedStart = newStart;
8342 if (ims.mChangedStart > newEnd) {
8343 ims.mChangedStart = newEnd;
8347 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8348 + oldStart + "-" + oldEnd + ","
8349 + newStart + "-" + newEnd + " " + what);
8350 ims.mContentChanged = true;
8355 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8356 what instanceof SpellCheckSpan) {
8357 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8365 public void dispatchFinishTemporaryDetach() {
8366 mDispatchTemporaryDetach = true;
8367 super.dispatchFinishTemporaryDetach();
8368 mDispatchTemporaryDetach = false;
8372 public void onStartTemporaryDetach() {
8373 super.onStartTemporaryDetach();
8374 // Only track when onStartTemporaryDetach() is called directly,
8375 // usually because this instance is an editable field in a list
8376 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8378 // Tell the editor that we are temporarily detached. It can use this to preserve
8379 // selection state as needed.
8380 if (mEditor != null) mEditor.mTemporaryDetach = true;
8384 public void onFinishTemporaryDetach() {
8385 super.onFinishTemporaryDetach();
8386 // Only track when onStartTemporaryDetach() is called directly,
8387 // usually because this instance is an editable field in a list
8388 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8389 if (mEditor != null) mEditor.mTemporaryDetach = false;
8393 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8394 if (mTemporaryDetach) {
8395 // If we are temporarily in the detach state, then do nothing.
8396 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8400 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8403 if (mText instanceof Spannable) {
8404 Spannable sp = (Spannable) mText;
8405 MetaKeyKeyListener.resetMetaState(sp);
8409 startStopMarquee(focused);
8411 if (mTransformation != null) {
8412 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8415 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8419 public void onWindowFocusChanged(boolean hasWindowFocus) {
8420 super.onWindowFocusChanged(hasWindowFocus);
8422 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8424 startStopMarquee(hasWindowFocus);
8428 protected void onVisibilityChanged(View changedView, int visibility) {
8429 super.onVisibilityChanged(changedView, visibility);
8430 if (mEditor != null && visibility != VISIBLE) {
8431 mEditor.hideCursorAndSpanControllers();
8432 stopTextActionMode();
8437 * Use {@link BaseInputConnection#removeComposingSpans
8438 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8439 * state from this text view.
8441 public void clearComposingText() {
8442 if (mText instanceof Spannable) {
8443 BaseInputConnection.removeComposingSpans((Spannable)mText);
8448 public void setSelected(boolean selected) {
8449 boolean wasSelected = isSelected();
8451 super.setSelected(selected);
8453 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8463 public boolean onTouchEvent(MotionEvent event) {
8464 final int action = event.getActionMasked();
8465 if (mEditor != null) {
8466 mEditor.onTouchEvent(event);
8468 if (mEditor.mSelectionModifierCursorController != null &&
8469 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8474 final boolean superResult = super.onTouchEvent(event);
8477 * Don't handle the release after a long press, because it will move the selection away from
8478 * whatever the menu action was trying to affect. If the long press should have triggered an
8479 * insertion action mode, we can now actually show it.
8481 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8482 mEditor.mDiscardNextActionUp = false;
8484 if (mEditor.mIsInsertionActionModeStartPending) {
8485 mEditor.startInsertionActionMode();
8486 mEditor.mIsInsertionActionModeStartPending = false;
8491 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8492 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8494 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8495 && mText instanceof Spannable && mLayout != null) {
8496 boolean handled = false;
8498 if (mMovement != null) {
8499 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8502 final boolean textIsSelectable = isTextSelectable();
8503 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8504 // The LinkMovementMethod which should handle taps on links has not been installed
8505 // on non editable text that support text selection.
8506 // We reproduce its behavior here to open links for these.
8507 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8508 getSelectionEnd(), ClickableSpan.class);
8510 if (links.length > 0) {
8511 links[0].onClick(this);
8516 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8517 // Show the IME, except when selecting in read-only text.
8518 final InputMethodManager imm = InputMethodManager.peekInstance();
8520 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8521 handled |= imm != null && imm.showSoftInput(this, 0);
8524 // The above condition ensures that the mEditor is not null
8525 mEditor.onTouchUpEvent(event);
8539 public boolean onGenericMotionEvent(MotionEvent event) {
8540 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8542 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8545 } catch (AbstractMethodError ex) {
8546 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8547 // Ignore its absence in case third party applications implemented the
8548 // interface directly.
8551 return super.onGenericMotionEvent(event);
8555 protected void onCreateContextMenu(ContextMenu menu) {
8556 if (mEditor != null) {
8557 mEditor.onCreateContextMenu(menu);
8562 public boolean showContextMenu() {
8563 if (mEditor != null) {
8564 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
8566 return super.showContextMenu();
8570 public boolean showContextMenu(float x, float y) {
8571 if (mEditor != null) {
8572 mEditor.setContextMenuAnchor(x, y);
8574 return super.showContextMenu(x, y);
8578 * @return True iff this TextView contains a text that can be edited, or if this is
8579 * a selectable TextView.
8581 boolean isTextEditable() {
8582 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8586 * Returns true, only while processing a touch gesture, if the initial
8587 * touch down event caused focus to move to the text view and as a result
8588 * its selection changed. Only valid while processing the touch gesture
8589 * of interest, in an editable text view.
8591 public boolean didTouchFocusSelect() {
8592 return mEditor != null && mEditor.mTouchFocusSelected;
8596 public void cancelLongPress() {
8597 super.cancelLongPress();
8598 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8602 public boolean onTrackballEvent(MotionEvent event) {
8603 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8604 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8609 return super.onTrackballEvent(event);
8612 public void setScroller(Scroller s) {
8617 protected float getLeftFadingEdgeStrength() {
8618 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8619 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8620 if (mMarquee != null && !mMarquee.isStopped()) {
8621 final Marquee marquee = mMarquee;
8622 if (marquee.shouldDrawLeftFade()) {
8623 final float scroll = marquee.getScroll();
8624 return scroll / getHorizontalFadingEdgeLength();
8628 } else if (getLineCount() == 1) {
8629 final int layoutDirection = getLayoutDirection();
8630 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8631 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8635 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8636 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8637 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8638 case Gravity.CENTER_HORIZONTAL:
8639 case Gravity.FILL_HORIZONTAL:
8640 final int textDirection = mLayout.getParagraphDirection(0);
8641 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8644 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8645 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8646 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8651 return super.getLeftFadingEdgeStrength();
8655 protected float getRightFadingEdgeStrength() {
8656 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8657 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8658 if (mMarquee != null && !mMarquee.isStopped()) {
8659 final Marquee marquee = mMarquee;
8660 final float maxFadeScroll = marquee.getMaxFadeScroll();
8661 final float scroll = marquee.getScroll();
8662 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8663 } else if (getLineCount() == 1) {
8664 final int layoutDirection = getLayoutDirection();
8665 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8666 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8668 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8669 getCompoundPaddingRight();
8670 final float lineWidth = mLayout.getLineWidth(0);
8671 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8674 case Gravity.CENTER_HORIZONTAL:
8675 case Gravity.FILL_HORIZONTAL:
8676 final int textDirection = mLayout.getParagraphDirection(0);
8677 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8680 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8681 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8682 getHorizontalFadingEdgeLength();
8687 return super.getRightFadingEdgeStrength();
8691 protected int computeHorizontalScrollRange() {
8692 if (mLayout != null) {
8693 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8694 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8697 return super.computeHorizontalScrollRange();
8701 protected int computeVerticalScrollRange() {
8702 if (mLayout != null)
8703 return mLayout.getHeight();
8705 return super.computeVerticalScrollRange();
8709 protected int computeVerticalScrollExtent() {
8710 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8714 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8715 super.findViewsWithText(outViews, searched, flags);
8716 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8717 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8718 String searchedLowerCase = searched.toString().toLowerCase();
8719 String textLowerCase = mText.toString().toLowerCase();
8720 if (textLowerCase.contains(searchedLowerCase)) {
8726 public enum BufferType {
8727 NORMAL, SPANNABLE, EDITABLE,
8731 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8732 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8733 * if TextView_textColor was not set directly.
8737 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8738 if (attrs == null) {
8739 // Preserve behavior prior to removal of this API.
8740 throw new NullPointerException();
8743 // It's not safe to use this method from apps. The parameter 'attrs'
8744 // must have been obtained using the TextView filter array which is not
8745 // available to the SDK. As such, we grab a default TypedArray with the
8746 // right filter instead here.
8747 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8748 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8749 if (colors == null) {
8750 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8752 final TypedArray appearance = context.obtainStyledAttributes(
8753 ap, R.styleable.TextAppearance);
8754 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8755 appearance.recycle();
8764 * Returns the default color from the TextView_textColor attribute from the
8765 * AttributeSet, if set, or the default color from the
8766 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8767 * TextView_textColor was not set directly.
8771 public static int getTextColor(Context context, TypedArray attrs, int def) {
8772 final ColorStateList colors = getTextColors(context, attrs);
8773 if (colors == null) {
8776 return colors.getDefaultColor();
8781 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8782 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8783 // Handle Ctrl-only shortcuts.
8785 case KeyEvent.KEYCODE_A:
8786 if (canSelectText()) {
8787 return onTextContextMenuItem(ID_SELECT_ALL);
8790 case KeyEvent.KEYCODE_Z:
8792 return onTextContextMenuItem(ID_UNDO);
8795 case KeyEvent.KEYCODE_X:
8797 return onTextContextMenuItem(ID_CUT);
8800 case KeyEvent.KEYCODE_C:
8802 return onTextContextMenuItem(ID_COPY);
8805 case KeyEvent.KEYCODE_V:
8807 return onTextContextMenuItem(ID_PASTE);
8811 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8812 // Handle Ctrl-Shift shortcuts.
8814 case KeyEvent.KEYCODE_Z:
8816 return onTextContextMenuItem(ID_REDO);
8819 case KeyEvent.KEYCODE_V:
8821 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8825 return super.onKeyShortcut(keyCode, event);
8829 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8830 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8831 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8834 boolean canSelectText() {
8835 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8839 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8840 * The text must be spannable and the movement method must allow for arbitary selection.
8842 * See also {@link #canSelectText()}.
8844 boolean textCanBeSelected() {
8845 // prepareCursorController() relies on this method.
8846 // If you change this condition, make sure prepareCursorController is called anywhere
8847 // the value of this condition might be changed.
8848 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8849 return isTextEditable() ||
8850 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8853 private Locale getTextServicesLocale(boolean allowNullLocale) {
8854 // Start fetching the text services locale asynchronously.
8855 updateTextServicesLocaleAsync();
8856 // If !allowNullLocale and there is no cached text services locale, just return the default
8858 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8859 : mCurrentSpellCheckerLocaleCache;
8863 * This is a temporary method. Future versions may support multi-locale text.
8864 * Caveat: This method may not return the latest text services locale, but this should be
8865 * acceptable and it's more important to make this method asynchronous.
8867 * @return The locale that should be used for a word iterator
8868 * in this TextView, based on the current spell checker settings,
8869 * the current IME's locale, or the system default locale.
8870 * Please note that a word iterator in this TextView is different from another word iterator
8871 * used by SpellChecker.java of TextView. This method should be used for the former.
8874 // TODO: Support multi-locale
8875 // TODO: Update the text services locale immediately after the keyboard locale is switched
8876 // by catching intent of keyboard switch event
8877 public Locale getTextServicesLocale() {
8878 return getTextServicesLocale(false /* allowNullLocale */);
8882 * @return true if this TextView is specialized for showing and interacting with the extracted
8883 * text in a full-screen input method.
8886 public boolean isInExtractedMode() {
8891 * This is a temporary method. Future versions may support multi-locale text.
8892 * Caveat: This method may not return the latest spell checker locale, but this should be
8893 * acceptable and it's more important to make this method asynchronous.
8895 * @return The locale that should be used for a spell checker in this TextView,
8896 * based on the current spell checker settings, the current IME's locale, or the system default
8900 public Locale getSpellCheckerLocale() {
8901 return getTextServicesLocale(true /* allowNullLocale */);
8904 private void updateTextServicesLocaleAsync() {
8905 // AsyncTask.execute() uses a serial executor which means we don't have
8906 // to lock around updateTextServicesLocaleLocked() to prevent it from
8907 // being executed n times in parallel.
8908 AsyncTask.execute(new Runnable() {
8911 updateTextServicesLocaleLocked();
8916 private void updateTextServicesLocaleLocked() {
8917 final TextServicesManager textServicesManager = (TextServicesManager)
8918 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8919 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8920 final Locale locale;
8921 if (subtype != null) {
8922 locale = subtype.getLocaleObject();
8926 mCurrentSpellCheckerLocaleCache = locale;
8929 void onLocaleChanged() {
8930 // Will be re-created on demand in getWordIterator with the proper new locale
8931 mEditor.mWordIterator = null;
8935 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8936 * Made available to achieve a consistent behavior.
8939 public WordIterator getWordIterator() {
8940 if (mEditor != null) {
8941 return mEditor.getWordIterator();
8949 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8950 super.onPopulateAccessibilityEventInternal(event);
8952 final CharSequence text = getTextForAccessibility();
8953 if (!TextUtils.isEmpty(text)) {
8954 event.getText().add(text);
8959 * @return true if the user has explicitly allowed accessibility services
8960 * to speak passwords.
8962 private boolean shouldSpeakPasswordsForAccessibility() {
8963 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8964 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8965 UserHandle.USER_CURRENT_OR_SELF) == 1);
8969 public CharSequence getAccessibilityClassName() {
8970 return TextView.class.getName();
8974 public void onProvideStructure(ViewStructure structure) {
8975 super.onProvideStructure(structure);
8976 final boolean isPassword = hasPasswordTransformationMethod()
8977 || isPasswordInputType(getInputType());
8979 if (mLayout == null) {
8982 Layout layout = mLayout;
8983 final int lineCount = layout.getLineCount();
8984 if (lineCount <= 1) {
8985 // Simple case: this is a single line.
8986 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8988 // Complex case: multi-line, could be scrolled or within a scroll container
8989 // so some lines are not visible.
8990 final int[] tmpCords = new int[2];
8991 getLocationInWindow(tmpCords);
8992 final int topWindowLocation = tmpCords[1];
8994 ViewParent viewParent = getParent();
8995 while (viewParent instanceof View) {
8996 root = (View) viewParent;
8997 viewParent = root.getParent();
8999 final int windowHeight = root.getHeight();
9001 final int bottomLine;
9002 if (topWindowLocation >= 0) {
9003 // The top of the view is fully within its window; start text at line 0.
9004 topLine = getLineAtCoordinateUnclamped(0);
9005 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
9007 // The top of hte window has scrolled off the top of the window; figure out
9008 // the starting line for this.
9009 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
9010 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
9012 // We want to return some contextual lines above/below the lines that are
9013 // actually visible.
9014 int expandedTopLine = topLine - (bottomLine-topLine)/2;
9015 if (expandedTopLine < 0) {
9016 expandedTopLine = 0;
9018 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
9019 if (expandedBottomLine >= lineCount) {
9020 expandedBottomLine = lineCount-1;
9022 // Convert lines into character offsets.
9023 int expandedTopChar = layout.getLineStart(expandedTopLine);
9024 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
9025 // Take into account selection -- if there is a selection, we need to expand
9026 // the text we are returning to include that selection.
9027 final int selStart = getSelectionStart();
9028 final int selEnd = getSelectionEnd();
9029 if (selStart < selEnd) {
9030 if (selStart < expandedTopChar) {
9031 expandedTopChar = selStart;
9033 if (selEnd > expandedBottomChar) {
9034 expandedBottomChar = selEnd;
9037 // Get the text and trim it to the range we are reporting.
9038 CharSequence text = getText();
9039 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9040 text = text.subSequence(expandedTopChar, expandedBottomChar);
9042 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
9043 final int[] lineOffsets = new int[bottomLine-topLine+1];
9044 final int[] lineBaselines = new int[bottomLine-topLine+1];
9045 final int baselineOffset = getBaselineOffset();
9046 for (int i=topLine; i<=bottomLine; i++) {
9047 lineOffsets[i-topLine] = layout.getLineStart(i);
9048 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
9050 structure.setTextLines(lineOffsets, lineBaselines);
9053 // Extract style information that applies to the TextView as a whole.
9055 int typefaceStyle = getTypefaceStyle();
9056 if ((typefaceStyle & Typeface.BOLD) != 0) {
9057 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9059 if ((typefaceStyle & Typeface.ITALIC) != 0) {
9060 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9063 // Global styles can also be set via TextView.setPaintFlags().
9064 int paintFlags = mTextPaint.getFlags();
9065 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9066 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9068 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9069 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9071 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9072 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9075 // TextView does not have its own text background color. A background is either part
9076 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9077 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9078 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
9080 structure.setHint(getHint());
9085 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9086 super.onInitializeAccessibilityEventInternal(event);
9088 final boolean isPassword = hasPasswordTransformationMethod();
9089 event.setPassword(isPassword);
9091 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9092 event.setFromIndex(Selection.getSelectionStart(mText));
9093 event.setToIndex(Selection.getSelectionEnd(mText));
9094 event.setItemCount(mText.length());
9100 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9101 super.onInitializeAccessibilityNodeInfoInternal(info);
9103 final boolean isPassword = hasPasswordTransformationMethod();
9104 info.setPassword(isPassword);
9105 info.setText(getTextForAccessibility());
9107 if (mBufferType == BufferType.EDITABLE) {
9108 info.setEditable(true);
9111 if (mEditor != null) {
9112 info.setInputType(mEditor.mInputType);
9114 if (mEditor.mError != null) {
9115 info.setContentInvalid(true);
9116 info.setError(mEditor.mError);
9120 if (!TextUtils.isEmpty(mText)) {
9121 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9122 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9123 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9124 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9125 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9126 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9127 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
9128 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
9133 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9136 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9139 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9142 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9143 ACCESSIBILITY_ACTION_SHARE,
9144 getResources().getString(com.android.internal.R.string.share)));
9146 if (canProcessText()) { // also implies mEditor is not null.
9147 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9151 // Check for known input filter types.
9152 final int numFilters = mFilters.length;
9153 for (int i = 0; i < numFilters; i++) {
9154 final InputFilter filter = mFilters[i];
9155 if (filter instanceof InputFilter.LengthFilter) {
9156 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9160 if (!isSingleLine()) {
9161 info.setMultiLine(true);
9166 * Performs an accessibility action after it has been offered to the
9172 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
9174 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9178 case AccessibilityNodeInfo.ACTION_CLICK: {
9179 return performAccessibilityActionClick(arguments);
9181 case AccessibilityNodeInfo.ACTION_COPY: {
9182 if (isFocused() && canCopy()) {
9183 if (onTextContextMenuItem(ID_COPY)) {
9188 case AccessibilityNodeInfo.ACTION_PASTE: {
9189 if (isFocused() && canPaste()) {
9190 if (onTextContextMenuItem(ID_PASTE)) {
9195 case AccessibilityNodeInfo.ACTION_CUT: {
9196 if (isFocused() && canCut()) {
9197 if (onTextContextMenuItem(ID_CUT)) {
9202 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9203 ensureIterableTextForAccessibilitySelectable();
9204 CharSequence text = getIterableTextForAccessibility();
9208 final int start = (arguments != null) ? arguments.getInt(
9209 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9210 final int end = (arguments != null) ? arguments.getInt(
9211 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9212 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9213 // No arguments clears the selection.
9214 if (start == end && end == -1) {
9215 Selection.removeSelection((Spannable) text);
9218 if (start >= 0 && start <= end && end <= text.length()) {
9219 Selection.setSelection((Spannable) text, start, end);
9220 // Make sure selection mode is engaged.
9221 if (mEditor != null) {
9222 mEditor.startSelectionActionMode();
9228 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9229 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9230 ensureIterableTextForAccessibilitySelectable();
9231 return super.performAccessibilityActionInternal(action, arguments);
9233 case ACCESSIBILITY_ACTION_SHARE: {
9234 if (isFocused() && canShare()) {
9235 if (onTextContextMenuItem(ID_SHARE)) {
9241 return super.performAccessibilityActionInternal(action, arguments);
9246 private boolean performAccessibilityActionClick(Bundle arguments) {
9247 boolean handled = false;
9248 boolean processed = false;
9254 if (arguments != null && arguments.containsKey(
9255 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT)) {
9256 int spanIndex = arguments.getInt(
9257 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT, -1);
9258 if (spanIndex >= 0 && hasSpannableText()) {
9259 ClickableSpan[] spans = ((Spannable) mText).getSpans(0,
9260 mText.length(), ClickableSpan.class);
9261 if (spans != null && spans.length > spanIndex && spans[spanIndex] != null) {
9262 // Simulate View.onTouchEvent for an ACTION_UP event
9263 if (isFocusable() && !isFocused()) {
9266 spans[spanIndex].onClick(this);
9273 if (!processed && arguments != null && arguments.containsKey(
9274 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT)) {
9275 int characterIndex = arguments.getInt(
9276 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT, -1);
9277 if (characterIndex >= 0 && hasSpannableText()) {
9278 ClickableSpan[] spans = ((Spannable) mText).getSpans(characterIndex,
9279 characterIndex, ClickableSpan.class);
9280 // click only on the first span to keep parity with onTouch() implementation
9281 if (spans != null && spans.length > 0 && spans[0] != null) {
9282 // Simulate View.onTouchEvent for an ACTION_UP event
9283 if (isFocusable() && !isFocused()) {
9286 spans[0].onClick(this);
9293 if (!processed && (isClickable() || isLongClickable())) {
9294 // Simulate View.onTouchEvent for an ACTION_UP event
9295 if (isFocusable() && !isFocused()) {
9303 // Show the IME, except when selecting in read-only text.
9304 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9305 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9306 final InputMethodManager imm = InputMethodManager.peekInstance();
9308 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9309 handled |= imm.showSoftInput(this, 0);
9316 private boolean hasSpannableText() {
9317 return mText != null && mText instanceof Spannable;
9322 public void sendAccessibilityEventInternal(int eventType) {
9323 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9324 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9327 // Do not send scroll events since first they are not interesting for
9328 // accessibility and second such events a generated too frequently.
9329 // For details see the implementation of bringTextIntoView().
9330 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9333 super.sendAccessibilityEventInternal(eventType);
9337 * Returns the text that should be exposed to accessibility services.
9339 * This approximates what is displayed visually. If the user has specified
9340 * that accessibility services should speak passwords, this method will
9341 * bypass any password transformation method and return unobscured text.
9343 * @return the text that should be exposed to accessibility services, may
9344 * be {@code null} if no text is set
9347 private CharSequence getTextForAccessibility() {
9348 // If the text is empty, we must be showing the hint text.
9349 if (TextUtils.isEmpty(mText)) {
9353 // Check whether we need to bypass the transformation
9354 // method and expose unobscured text.
9355 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9359 // Otherwise, speak whatever text is being displayed.
9360 return mTransformed;
9363 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9364 int fromIndex, int removedCount, int addedCount) {
9365 AccessibilityEvent event =
9366 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9367 event.setFromIndex(fromIndex);
9368 event.setRemovedCount(removedCount);
9369 event.setAddedCount(addedCount);
9370 event.setBeforeText(beforeText);
9371 sendAccessibilityEventUnchecked(event);
9375 * Returns whether this text view is a current input method target. The
9376 * default implementation just checks with {@link InputMethodManager}.
9378 public boolean isInputMethodTarget() {
9379 InputMethodManager imm = InputMethodManager.peekInstance();
9380 return imm != null && imm.isActive(this);
9383 static final int ID_SELECT_ALL = android.R.id.selectAll;
9384 static final int ID_UNDO = android.R.id.undo;
9385 static final int ID_REDO = android.R.id.redo;
9386 static final int ID_CUT = android.R.id.cut;
9387 static final int ID_COPY = android.R.id.copy;
9388 static final int ID_PASTE = android.R.id.paste;
9389 static final int ID_SHARE = android.R.id.shareText;
9390 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9391 static final int ID_REPLACE = android.R.id.replaceText;
9394 * Called when a context menu option for the text view is selected. Currently
9395 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9396 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9398 * @return true if the context menu item action was performed.
9400 public boolean onTextContextMenuItem(int id) {
9402 int max = mText.length();
9405 final int selStart = getSelectionStart();
9406 final int selEnd = getSelectionEnd();
9408 min = Math.max(0, Math.min(selStart, selEnd));
9409 max = Math.max(0, Math.max(selStart, selEnd));
9414 // This starts an action mode if triggered from another action mode. Text is
9415 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9416 // true even if text is empty.
9417 boolean shouldRestartActionMode =
9418 mEditor != null && mEditor.mTextActionMode != null;
9419 stopTextActionMode();
9421 if (shouldRestartActionMode) {
9422 mEditor.startSelectionActionMode();
9427 if (mEditor != null) {
9430 return true; // Returns true even if nothing was undone.
9433 if (mEditor != null) {
9436 return true; // Returns true even if nothing was undone.
9439 paste(min, max, true /* withFormatting */);
9442 case ID_PASTE_AS_PLAIN_TEXT:
9443 paste(min, max, false /* withFormatting */);
9447 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9448 deleteText_internal(min, max);
9449 stopTextActionMode();
9453 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9454 stopTextActionMode();
9458 if (mEditor != null) {
9464 shareSelectedText();
9470 CharSequence getTransformedText(int start, int end) {
9471 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9475 public boolean performLongClick() {
9476 boolean handled = false;
9478 if (mEditor != null) {
9479 mEditor.mIsBeingLongClicked = true;
9482 if (super.performLongClick()) {
9486 if (mEditor != null) {
9487 handled |= mEditor.performLongClick(handled);
9488 mEditor.mIsBeingLongClicked = false;
9492 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9493 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9500 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9501 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9502 if (mEditor != null) {
9503 mEditor.onScrollChanged();
9508 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9509 * by the IME or by the spell checker as the user types. This is done by adding
9510 * {@link SuggestionSpan}s to the text.
9512 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9513 * user asks for them on these parts of the text. This value depends on the inputType of this
9516 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9518 * In addition, the type variation must be one of
9519 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9520 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9521 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9522 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9523 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9525 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9527 * @return true if the suggestions popup window is enabled, based on the inputType.
9529 public boolean isSuggestionsEnabled() {
9530 if (mEditor == null) return false;
9531 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9534 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9536 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9537 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9538 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9539 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9540 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9541 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9545 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9546 * selection is initiated in this View.
9548 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9549 * Paste, Replace and Share actions, depending on what this View supports.
9551 * <p>A custom implementation can add new entries in the default menu in its
9552 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9553 * method. The default actions can also be removed from the menu using
9554 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9555 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9556 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9558 * <p>Returning false from
9559 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9560 * will prevent the action mode from being started.
9562 * <p>Action click events should be handled by the custom implementation of
9563 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9564 * android.view.MenuItem)}.
9566 * <p>Note that text selection mode is not started when a TextView receives focus and the
9567 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9568 * that case, to allow for quick replacement.
9570 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9571 createEditorIfNeeded();
9572 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9576 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9578 * @return The current custom selection callback.
9580 public ActionMode.Callback getCustomSelectionActionModeCallback() {
9581 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9585 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9586 * insertion is initiated in this View.
9587 * The standard implementation populates the menu with a subset of Select All,
9588 * Paste and Replace actions, depending on what this View supports.
9590 * <p>A custom implementation can add new entries in the default menu in its
9591 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9592 * android.view.Menu)} method. The default actions can also be removed from the menu using
9593 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9594 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9596 * <p>Returning false from
9597 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9598 * android.view.Menu)} will prevent the action mode from being started.</p>
9600 * <p>Action click events should be handled by the custom implementation of
9601 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9602 * android.view.MenuItem)}.</p>
9604 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9605 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9607 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9608 createEditorIfNeeded();
9609 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9613 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9615 * @return The current custom insertion callback.
9617 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9618 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9624 protected void stopTextActionMode() {
9625 if (mEditor != null) {
9626 mEditor.stopTextActionMode();
9631 return mEditor != null && mEditor.canUndo();
9635 return mEditor != null && mEditor.canRedo();
9639 if (hasPasswordTransformationMethod()) {
9643 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9644 mEditor.mKeyListener != null) {
9652 if (hasPasswordTransformationMethod()) {
9656 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9663 boolean canShare() {
9664 return canCopy() && isDeviceProvisioned();
9667 boolean isDeviceProvisioned() {
9668 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
9669 mDeviceProvisionedState = Settings.Global.getInt(
9670 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
9671 ? DEVICE_PROVISIONED_YES
9672 : DEVICE_PROVISIONED_NO;
9674 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
9677 boolean canPaste() {
9678 return (mText instanceof Editable &&
9679 mEditor != null && mEditor.mKeyListener != null &&
9680 getSelectionStart() >= 0 &&
9681 getSelectionEnd() >= 0 &&
9682 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9686 boolean canProcessText() {
9687 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9688 || hasPasswordTransformationMethod()) {
9692 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9699 boolean canSelectAllText() {
9700 return canSelectText() && !hasPasswordTransformationMethod()
9701 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9704 boolean selectAllText() {
9705 // Need to hide insert point cursor controller before settings selection, otherwise insert
9706 // point cursor controller obtains cursor update event and update cursor with cancelling
9708 if (mEditor != null) {
9709 mEditor.hideInsertionPointCursorController();
9711 final int length = mText.length();
9712 Selection.setSelection((Spannable) mText, 0, length);
9716 void replaceSelectionWithText(CharSequence text) {
9717 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9721 * Paste clipboard content between min and max positions.
9723 private void paste(int min, int max, boolean withFormatting) {
9724 ClipboardManager clipboard =
9725 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9726 ClipData clip = clipboard.getPrimaryClip();
9728 boolean didFirst = false;
9729 for (int i=0; i<clip.getItemCount(); i++) {
9730 final CharSequence paste;
9731 if (withFormatting) {
9732 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9734 // Get an item as text and remove all spans by toString().
9735 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9736 paste = (text instanceof Spanned) ? text.toString() : text;
9738 if (paste != null) {
9740 Selection.setSelection((Spannable) mText, max);
9741 ((Editable) mText).replace(min, max, paste);
9744 ((Editable) mText).insert(getSelectionEnd(), "\n");
9745 ((Editable) mText).insert(getSelectionEnd(), paste);
9749 stopTextActionMode();
9750 sLastCutCopyOrTextChangedTime = 0;
9754 private void shareSelectedText() {
9755 String selectedText = getSelectedText();
9756 if (selectedText != null && !selectedText.isEmpty()) {
9757 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9758 sharingIntent.setType("text/plain");
9759 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9760 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9761 getContext().startActivity(Intent.createChooser(sharingIntent, null));
9762 stopTextActionMode();
9766 private void setPrimaryClip(ClipData clip) {
9767 ClipboardManager clipboard = (ClipboardManager) getContext().
9768 getSystemService(Context.CLIPBOARD_SERVICE);
9769 clipboard.setPrimaryClip(clip);
9770 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9774 * Get the character offset closest to the specified absolute position. A typical use case is to
9775 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9777 * @param x The horizontal absolute position of a point on screen
9778 * @param y The vertical absolute position of a point on screen
9779 * @return the character offset for the character whose position is closest to the specified
9780 * position. Returns -1 if there is no layout.
9782 public int getOffsetForPosition(float x, float y) {
9783 if (getLayout() == null) return -1;
9784 final int line = getLineAtCoordinate(y);
9785 final int offset = getOffsetAtCoordinate(line, x);
9789 float convertToLocalHorizontalCoordinate(float x) {
9790 x -= getTotalPaddingLeft();
9791 // Clamp the position to inside of the view.
9792 x = Math.max(0.0f, x);
9793 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9798 int getLineAtCoordinate(float y) {
9799 y -= getTotalPaddingTop();
9800 // Clamp the position to inside of the view.
9801 y = Math.max(0.0f, y);
9802 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9804 return getLayout().getLineForVertical((int) y);
9807 int getLineAtCoordinateUnclamped(float y) {
9808 y -= getTotalPaddingTop();
9810 return getLayout().getLineForVertical((int) y);
9813 int getOffsetAtCoordinate(int line, float x) {
9814 x = convertToLocalHorizontalCoordinate(x);
9815 return getLayout().getOffsetForHorizontal(line, x);
9819 public boolean onDragEvent(DragEvent event) {
9820 switch (event.getAction()) {
9821 case DragEvent.ACTION_DRAG_STARTED:
9822 return mEditor != null && mEditor.hasInsertionController();
9824 case DragEvent.ACTION_DRAG_ENTERED:
9825 TextView.this.requestFocus();
9828 case DragEvent.ACTION_DRAG_LOCATION:
9829 final int offset = getOffsetForPosition(event.getX(), event.getY());
9830 Selection.setSelection((Spannable)mText, offset);
9833 case DragEvent.ACTION_DROP:
9834 if (mEditor != null) mEditor.onDrop(event);
9837 case DragEvent.ACTION_DRAG_ENDED:
9838 case DragEvent.ACTION_DRAG_EXITED:
9844 boolean isInBatchEditMode() {
9845 if (mEditor == null) return false;
9846 final Editor.InputMethodState ims = mEditor.mInputMethodState;
9848 return ims.mBatchEditNesting > 0;
9850 return mEditor.mInBatchEditControllers;
9854 public void onRtlPropertiesChanged(int layoutDirection) {
9855 super.onRtlPropertiesChanged(layoutDirection);
9857 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9858 if (mTextDir != newTextDir) {
9859 mTextDir = newTextDir;
9860 if (mLayout != null) {
9869 protected TextDirectionHeuristic getTextDirectionHeuristic() {
9870 if (hasPasswordTransformationMethod()) {
9871 // passwords fields should be LTR
9872 return TextDirectionHeuristics.LTR;
9875 // Always need to resolve layout direction first
9876 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9878 // Now, we can select the heuristic
9879 switch (getTextDirection()) {
9881 case TEXT_DIRECTION_FIRST_STRONG:
9882 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9883 TextDirectionHeuristics.FIRSTSTRONG_LTR);
9884 case TEXT_DIRECTION_ANY_RTL:
9885 return TextDirectionHeuristics.ANYRTL_LTR;
9886 case TEXT_DIRECTION_LTR:
9887 return TextDirectionHeuristics.LTR;
9888 case TEXT_DIRECTION_RTL:
9889 return TextDirectionHeuristics.RTL;
9890 case TEXT_DIRECTION_LOCALE:
9891 return TextDirectionHeuristics.LOCALE;
9892 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9893 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9894 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9895 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9903 public void onResolveDrawables(int layoutDirection) {
9904 // No need to resolve twice
9905 if (mLastLayoutDirection == layoutDirection) {
9908 mLastLayoutDirection = layoutDirection;
9910 // Resolve drawables
9911 if (mDrawables != null) {
9912 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
9913 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
9914 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
9915 applyCompoundDrawableTint();
9921 * Prepares a drawable for display by propagating layout direction and
9924 * @param dr the drawable to prepare
9926 private void prepareDrawableForDisplay(@Nullable Drawable dr) {
9931 dr.setLayoutDirection(getLayoutDirection());
9933 if (dr.isStateful()) {
9934 dr.setState(getDrawableState());
9935 dr.jumpToCurrentState();
9942 protected void resetResolvedDrawables() {
9943 super.resetResolvedDrawables();
9944 mLastLayoutDirection = -1;
9950 protected void viewClicked(InputMethodManager imm) {
9952 imm.viewClicked(this);
9957 * Deletes the range of text [start, end[.
9960 protected void deleteText_internal(int start, int end) {
9961 ((Editable) mText).delete(start, end);
9965 * Replaces the range of text [start, end[ by replacement text
9968 protected void replaceText_internal(int start, int end, CharSequence text) {
9969 ((Editable) mText).replace(start, end, text);
9973 * Sets a span on the specified range of text
9976 protected void setSpan_internal(Object span, int start, int end, int flags) {
9977 ((Editable) mText).setSpan(span, start, end, flags);
9981 * Moves the cursor to the specified offset position in text
9984 protected void setCursorPosition_internal(int start, int end) {
9985 Selection.setSelection(((Editable) mText), start, end);
9989 * An Editor should be created as soon as any of the editable-specific fields (grouped
9990 * inside the Editor object) is assigned to a non-default value.
9991 * This method will create the Editor if needed.
9993 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9994 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9995 * Editor for backward compatibility, as soon as one of these fields is assigned.
9997 * Also note that for performance reasons, the mEditor is created when needed, but not
9998 * reset when no more edit-specific fields are needed.
10000 private void createEditorIfNeeded() {
10001 if (mEditor == null) {
10002 mEditor = new Editor(this);
10010 public CharSequence getIterableTextForAccessibility() {
10014 private void ensureIterableTextForAccessibilitySelectable() {
10015 if (!(mText instanceof Spannable)) {
10016 setText(mText, BufferType.SPANNABLE);
10024 public TextSegmentIterator getIteratorForGranularity(int granularity) {
10025 switch (granularity) {
10026 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
10027 Spannable text = (Spannable) getIterableTextForAccessibility();
10028 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10029 AccessibilityIterators.LineTextSegmentIterator iterator =
10030 AccessibilityIterators.LineTextSegmentIterator.getInstance();
10031 iterator.initialize(text, getLayout());
10035 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
10036 Spannable text = (Spannable) getIterableTextForAccessibility();
10037 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10038 AccessibilityIterators.PageTextSegmentIterator iterator =
10039 AccessibilityIterators.PageTextSegmentIterator.getInstance();
10040 iterator.initialize(this);
10045 return super.getIteratorForGranularity(granularity);
10052 public int getAccessibilitySelectionStart() {
10053 return getSelectionStart();
10059 public boolean isAccessibilitySelectionExtendable() {
10067 public int getAccessibilitySelectionEnd() {
10068 return getSelectionEnd();
10075 public void setAccessibilitySelection(int start, int end) {
10076 if (getAccessibilitySelectionStart() == start
10077 && getAccessibilitySelectionEnd() == end) {
10080 // Hide all selection controllers used for adjusting selection
10081 // since we are doing so explicitlty by other means and these
10082 // controllers interact with how selection behaves.
10083 if (mEditor != null) {
10084 mEditor.hideCursorAndSpanControllers();
10085 mEditor.stopTextActionMode();
10087 CharSequence text = getIterableTextForAccessibility();
10088 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
10089 Selection.setSelection((Spannable) text, start, end);
10091 Selection.removeSelection((Spannable) text);
10097 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10098 super.encodeProperties(stream);
10100 TruncateAt ellipsize = getEllipsize();
10101 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10102 stream.addProperty("text:textSize", getTextSize());
10103 stream.addProperty("text:scaledTextSize", getScaledTextSize());
10104 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10105 stream.addProperty("text:selectionStart", getSelectionStart());
10106 stream.addProperty("text:selectionEnd", getSelectionEnd());
10107 stream.addProperty("text:curTextColor", mCurTextColor);
10108 stream.addProperty("text:text", mText == null ? null : mText.toString());
10109 stream.addProperty("text:gravity", mGravity);
10113 * User interface state that is stored by TextView for implementing
10114 * {@link View#onSaveInstanceState}.
10116 public static class SavedState extends BaseSavedState {
10120 boolean frozenWithFocus;
10121 CharSequence error;
10122 ParcelableParcel editorState; // Optional state from Editor.
10124 SavedState(Parcelable superState) {
10129 public void writeToParcel(Parcel out, int flags) {
10130 super.writeToParcel(out, flags);
10131 out.writeInt(selStart);
10132 out.writeInt(selEnd);
10133 out.writeInt(frozenWithFocus ? 1 : 0);
10134 TextUtils.writeToParcel(text, out, flags);
10136 if (error == null) {
10140 TextUtils.writeToParcel(error, out, flags);
10143 if (editorState == null) {
10147 editorState.writeToParcel(out, flags);
10152 public String toString() {
10153 String str = "TextView.SavedState{"
10154 + Integer.toHexString(System.identityHashCode(this))
10155 + " start=" + selStart + " end=" + selEnd;
10156 if (text != null) {
10157 str += " text=" + text;
10162 @SuppressWarnings("hiding")
10163 public static final Parcelable.Creator<SavedState> CREATOR
10164 = new Parcelable.Creator<SavedState>() {
10165 public SavedState createFromParcel(Parcel in) {
10166 return new SavedState(in);
10169 public SavedState[] newArray(int size) {
10170 return new SavedState[size];
10174 private SavedState(Parcel in) {
10176 selStart = in.readInt();
10177 selEnd = in.readInt();
10178 frozenWithFocus = (in.readInt() != 0);
10179 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10181 if (in.readInt() != 0) {
10182 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10185 if (in.readInt() != 0) {
10186 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10191 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10192 private char[] mChars;
10193 private int mStart, mLength;
10195 public CharWrapper(char[] chars, int start, int len) {
10201 /* package */ void set(char[] chars, int start, int len) {
10207 public int length() {
10211 public char charAt(int off) {
10212 return mChars[off + mStart];
10216 public String toString() {
10217 return new String(mChars, mStart, mLength);
10220 public CharSequence subSequence(int start, int end) {
10221 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10222 throw new IndexOutOfBoundsException(start + ", " + end);
10225 return new String(mChars, start + mStart, end - start);
10228 public void getChars(int start, int end, char[] buf, int off) {
10229 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10230 throw new IndexOutOfBoundsException(start + ", " + end);
10233 System.arraycopy(mChars, start + mStart, buf, off, end - start);
10236 public void drawText(Canvas c, int start, int end,
10237 float x, float y, Paint p) {
10238 c.drawText(mChars, start + mStart, end - start, x, y, p);
10241 public void drawTextRun(Canvas c, int start, int end,
10242 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
10243 int count = end - start;
10244 int contextCount = contextEnd - contextStart;
10245 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
10246 contextCount, x, y, isRtl, p);
10249 public float measureText(int start, int end, Paint p) {
10250 return p.measureText(mChars, start + mStart, end - start);
10253 public int getTextWidths(int start, int end, float[] widths, Paint p) {
10254 return p.getTextWidths(mChars, start + mStart, end - start, widths);
10257 public float getTextRunAdvances(int start, int end, int contextStart,
10258 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
10260 int count = end - start;
10261 int contextCount = contextEnd - contextStart;
10262 return p.getTextRunAdvances(mChars, start + mStart, count,
10263 contextStart + mStart, contextCount, isRtl, advances,
10267 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
10268 int offset, int cursorOpt, Paint p) {
10269 int contextCount = contextEnd - contextStart;
10270 return p.getTextRunCursor(mChars, contextStart + mStart,
10271 contextCount, dir, offset + mStart, cursorOpt);
10275 private static final class Marquee {
10276 // TODO: Add an option to configure this
10277 private static final float MARQUEE_DELTA_MAX = 0.07f;
10278 private static final int MARQUEE_DELAY = 1200;
10279 private static final int MARQUEE_DP_PER_SECOND = 30;
10281 private static final byte MARQUEE_STOPPED = 0x0;
10282 private static final byte MARQUEE_STARTING = 0x1;
10283 private static final byte MARQUEE_RUNNING = 0x2;
10285 private final WeakReference<TextView> mView;
10286 private final Choreographer mChoreographer;
10288 private byte mStatus = MARQUEE_STOPPED;
10289 private final float mPixelsPerSecond;
10290 private float mMaxScroll;
10291 private float mMaxFadeScroll;
10292 private float mGhostStart;
10293 private float mGhostOffset;
10294 private float mFadeStop;
10295 private int mRepeatLimit;
10297 private float mScroll;
10298 private long mLastAnimationMs;
10300 Marquee(TextView v) {
10301 final float density = v.getContext().getResources().getDisplayMetrics().density;
10302 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10303 mView = new WeakReference<TextView>(v);
10304 mChoreographer = Choreographer.getInstance();
10307 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10309 public void doFrame(long frameTimeNanos) {
10314 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10316 public void doFrame(long frameTimeNanos) {
10317 mStatus = MARQUEE_RUNNING;
10318 mLastAnimationMs = mChoreographer.getFrameTime();
10323 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10325 public void doFrame(long frameTimeNanos) {
10326 if (mStatus == MARQUEE_RUNNING) {
10327 if (mRepeatLimit >= 0) {
10330 start(mRepeatLimit);
10336 if (mStatus != MARQUEE_RUNNING) {
10340 mChoreographer.removeFrameCallback(mTickCallback);
10342 final TextView textView = mView.get();
10343 if (textView != null && (textView.isFocused() || textView.isSelected())) {
10344 long currentMs = mChoreographer.getFrameTime();
10345 long deltaMs = currentMs - mLastAnimationMs;
10346 mLastAnimationMs = currentMs;
10347 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10348 mScroll += deltaPx;
10349 if (mScroll > mMaxScroll) {
10350 mScroll = mMaxScroll;
10351 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10353 mChoreographer.postFrameCallback(mTickCallback);
10355 textView.invalidate();
10360 mStatus = MARQUEE_STOPPED;
10361 mChoreographer.removeFrameCallback(mStartCallback);
10362 mChoreographer.removeFrameCallback(mRestartCallback);
10363 mChoreographer.removeFrameCallback(mTickCallback);
10367 private void resetScroll() {
10369 final TextView textView = mView.get();
10370 if (textView != null) textView.invalidate();
10373 void start(int repeatLimit) {
10374 if (repeatLimit == 0) {
10378 mRepeatLimit = repeatLimit;
10379 final TextView textView = mView.get();
10380 if (textView != null && textView.mLayout != null) {
10381 mStatus = MARQUEE_STARTING;
10383 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10384 textView.getCompoundPaddingRight();
10385 final float lineWidth = textView.mLayout.getLineWidth(0);
10386 final float gap = textWidth / 3.0f;
10387 mGhostStart = lineWidth - textWidth + gap;
10388 mMaxScroll = mGhostStart + textWidth;
10389 mGhostOffset = lineWidth + gap;
10390 mFadeStop = lineWidth + textWidth / 6.0f;
10391 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10393 textView.invalidate();
10394 mChoreographer.postFrameCallback(mStartCallback);
10398 float getGhostOffset() {
10399 return mGhostOffset;
10402 float getScroll() {
10406 float getMaxFadeScroll() {
10407 return mMaxFadeScroll;
10410 boolean shouldDrawLeftFade() {
10411 return mScroll <= mFadeStop;
10414 boolean shouldDrawGhost() {
10415 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10418 boolean isRunning() {
10419 return mStatus == MARQUEE_RUNNING;
10422 boolean isStopped() {
10423 return mStatus == MARQUEE_STOPPED;
10427 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10429 private CharSequence mBeforeText;
10431 public void beforeTextChanged(CharSequence buffer, int start,
10432 int before, int after) {
10433 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10434 + " before=" + before + " after=" + after + ": " + buffer);
10436 if (AccessibilityManager.getInstance(mContext).isEnabled()
10437 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10438 || shouldSpeakPasswordsForAccessibility())) {
10439 mBeforeText = buffer.toString();
10442 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10445 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10446 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10447 + " before=" + before + " after=" + after + ": " + buffer);
10448 TextView.this.handleTextChanged(buffer, start, before, after);
10450 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10451 (isFocused() || isSelected() && isShown())) {
10452 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10453 mBeforeText = null;
10457 public void afterTextChanged(Editable buffer) {
10458 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10459 TextView.this.sendAfterTextChanged(buffer);
10461 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10462 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10466 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10467 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10468 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10469 TextView.this.spanChange(buf, what, s, st, e, en);
10472 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10473 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10474 + " what=" + what + ": " + buf);
10475 TextView.this.spanChange(buf, what, -1, s, -1, e);
10478 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10479 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10480 + " what=" + what + ": " + buf);
10481 TextView.this.spanChange(buf, what, s, -1, e, -1);