blob: 726b89a23f77b2059d135a0b6b8357de47f56b83 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Gilles Debunne78996c92010-10-12 16:01:47 -070019import android.R;
Tor Norbye80756e32015-03-02 09:39:27 -080020import android.annotation.ColorInt;
Tor Norbye7b9c9122013-05-30 16:48:33 -070021import android.annotation.DrawableRes;
Alan Viverette97f84ee2014-09-09 16:55:56 -070022import android.annotation.NonNull;
Raph Leviene272a262014-08-07 16:07:51 -070023import android.annotation.Nullable;
Tor Norbye7b9c9122013-05-30 16:48:33 -070024import android.annotation.StringRes;
25import android.annotation.StyleRes;
26import android.annotation.XmlRes;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070027import android.content.ClipData;
Gilles Debunne64e54a62010-09-07 19:07:17 -070028import android.content.ClipboardManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.content.Context;
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +000030import android.content.Intent;
Dianne Hackborn3aa49b62013-04-26 16:39:17 -070031import android.content.UndoManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.content.res.ColorStateList;
Christopher Tate1373a8e2011-11-10 19:59:13 -080033import android.content.res.CompatibilityInfo;
Raph Levien2e3aa442015-01-14 16:12:53 -080034import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.content.res.Resources;
36import android.content.res.TypedArray;
37import android.content.res.XmlResourceParser;
38import android.graphics.Canvas;
Philip Milne7b757812012-09-19 18:13:44 -070039import android.graphics.Insets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.graphics.Paint;
41import android.graphics.Path;
Alan Viveretteb97d6982015-01-07 16:16:20 -080042import android.graphics.PorterDuff;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043import android.graphics.Rect;
44import android.graphics.RectF;
45import android.graphics.Typeface;
46import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070047import android.inputmethodservice.ExtractEditText;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090048import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import android.os.Parcel;
51import android.os.Parcelable;
James Cookf59152c2015-02-26 18:03:58 -080052import android.os.ParcelableParcel;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.os.SystemClock;
Alan Viveretteb6e0cb92014-11-24 15:13:43 -080054import android.os.UserHandle;
alanv7d624192012-05-21 14:23:17 -070055import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import android.text.BoringLayout;
57import android.text.DynamicLayout;
58import android.text.Editable;
59import android.text.GetChars;
60import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070062import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063import android.text.Layout;
64import android.text.ParcelableSpan;
65import android.text.Selection;
66import android.text.SpanWatcher;
67import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070068import android.text.SpannableString;
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -070069import android.text.SpannableStringBuilder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import android.text.Spanned;
71import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070073import android.text.TextDirectionHeuristic;
74import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075import android.text.TextPaint;
76import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070077import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070078import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070079import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080080import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081import android.text.method.DateKeyListener;
82import android.text.method.DateTimeKeyListener;
83import android.text.method.DialerKeyListener;
84import android.text.method.DigitsKeyListener;
85import android.text.method.KeyListener;
86import android.text.method.LinkMovementMethod;
87import android.text.method.MetaKeyKeyListener;
88import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089import android.text.method.PasswordTransformationMethod;
90import android.text.method.SingleLineTransformationMethod;
91import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070092import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070094import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070095import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080096import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080097import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070099import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -0700100import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101import android.text.style.URLSpan;
102import android.text.style.UpdateAppearance;
103import android.text.util.Linkify;
104import android.util.AttributeSet;
svetoslavganov75986cf2009-05-14 22:28:01 -0700105import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700107import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -0700108import android.view.ActionMode;
Jorim Jaggi3aa42202014-07-01 14:59:34 +0200109import android.view.Choreographer;
Gilles Debunnef170a342010-11-11 11:08:59 -0800110import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700112import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800113import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115import android.view.MotionEvent;
116import android.view.View;
Dianne Hackborna83ce1d2015-03-11 15:16:13 -0700117import android.view.ViewAssistStructure;
Gilles Debunne65f60412010-10-15 16:18:35 -0700118import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700120import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700121import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700123import android.view.accessibility.AccessibilityEvent;
124import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700125import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126import android.view.animation.AnimationUtils;
127import android.view.inputmethod.BaseInputConnection;
128import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800129import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700130import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131import android.view.inputmethod.ExtractedText;
132import android.view.inputmethod.ExtractedTextRequest;
133import android.view.inputmethod.InputConnection;
134import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900135import android.view.textservice.SpellCheckerSubtype;
136import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137import android.widget.RemoteViews.RemoteView;
138
Gilles Debunne22378292011-08-12 10:38:52 -0700139import com.android.internal.util.FastMath;
140import com.android.internal.widget.EditableInputConnection;
141
142import org.xmlpull.v1.XmlPullParserException;
143
Gilles Debunne27113f82010-08-23 12:09:14 -0700144import java.io.IOException;
145import java.lang.ref.WeakReference;
146import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700147import java.util.Locale;
Gilles Debunne27113f82010-08-23 12:09:14 -0700148
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700149import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
150
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151/**
152 * Displays text to the user and optionally allows them to edit it. A TextView
153 * is a complete text editor, however the basic class is configured to not
154 * allow editing; see {@link EditText} for a subclass that configures the text
155 * view for editing.
156 *
157 * <p>
Joe Malin10d96952013-05-29 17:49:09 -0700158 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
159 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
160 * android:textIsSelectable} to "true" or call
161 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
162 * allows users to make selection gestures in the TextView, which in turn triggers the system's
163 * built-in copy/paste controls.
164 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 * <b>XML attributes</b>
166 * <p>
167 * See {@link android.R.styleable#TextView TextView Attributes},
168 * {@link android.R.styleable#View View Attributes}
169 *
170 * @attr ref android.R.styleable#TextView_text
171 * @attr ref android.R.styleable#TextView_bufferType
172 * @attr ref android.R.styleable#TextView_hint
173 * @attr ref android.R.styleable#TextView_textColor
174 * @attr ref android.R.styleable#TextView_textColorHighlight
175 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700176 * @attr ref android.R.styleable#TextView_textAppearance
177 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 * @attr ref android.R.styleable#TextView_textSize
179 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700180 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 * @attr ref android.R.styleable#TextView_typeface
182 * @attr ref android.R.styleable#TextView_textStyle
183 * @attr ref android.R.styleable#TextView_cursorVisible
184 * @attr ref android.R.styleable#TextView_maxLines
185 * @attr ref android.R.styleable#TextView_maxHeight
186 * @attr ref android.R.styleable#TextView_lines
187 * @attr ref android.R.styleable#TextView_height
188 * @attr ref android.R.styleable#TextView_minLines
189 * @attr ref android.R.styleable#TextView_minHeight
190 * @attr ref android.R.styleable#TextView_maxEms
191 * @attr ref android.R.styleable#TextView_maxWidth
192 * @attr ref android.R.styleable#TextView_ems
193 * @attr ref android.R.styleable#TextView_width
194 * @attr ref android.R.styleable#TextView_minEms
195 * @attr ref android.R.styleable#TextView_minWidth
196 * @attr ref android.R.styleable#TextView_gravity
197 * @attr ref android.R.styleable#TextView_scrollHorizontally
198 * @attr ref android.R.styleable#TextView_password
199 * @attr ref android.R.styleable#TextView_singleLine
200 * @attr ref android.R.styleable#TextView_selectAllOnFocus
201 * @attr ref android.R.styleable#TextView_includeFontPadding
202 * @attr ref android.R.styleable#TextView_maxLength
203 * @attr ref android.R.styleable#TextView_shadowColor
204 * @attr ref android.R.styleable#TextView_shadowDx
205 * @attr ref android.R.styleable#TextView_shadowDy
206 * @attr ref android.R.styleable#TextView_shadowRadius
207 * @attr ref android.R.styleable#TextView_autoLink
208 * @attr ref android.R.styleable#TextView_linksClickable
209 * @attr ref android.R.styleable#TextView_numeric
210 * @attr ref android.R.styleable#TextView_digits
211 * @attr ref android.R.styleable#TextView_phoneNumber
212 * @attr ref android.R.styleable#TextView_inputMethod
213 * @attr ref android.R.styleable#TextView_capitalize
214 * @attr ref android.R.styleable#TextView_autoText
215 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700216 * @attr ref android.R.styleable#TextView_freezesText
217 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 * @attr ref android.R.styleable#TextView_drawableTop
219 * @attr ref android.R.styleable#TextView_drawableBottom
220 * @attr ref android.R.styleable#TextView_drawableRight
221 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800222 * @attr ref android.R.styleable#TextView_drawableStart
223 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700224 * @attr ref android.R.styleable#TextView_drawablePadding
Alan Viveretteb97d6982015-01-07 16:16:20 -0800225 * @attr ref android.R.styleable#TextView_drawableTint
226 * @attr ref android.R.styleable#TextView_drawableTintMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 * @attr ref android.R.styleable#TextView_lineSpacingExtra
228 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
229 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700230 * @attr ref android.R.styleable#TextView_inputType
231 * @attr ref android.R.styleable#TextView_imeOptions
232 * @attr ref android.R.styleable#TextView_privateImeOptions
233 * @attr ref android.R.styleable#TextView_imeActionLabel
234 * @attr ref android.R.styleable#TextView_imeActionId
235 * @attr ref android.R.styleable#TextView_editorExtras
Raph Leviene1c4a0d2014-06-05 15:26:59 -0700236 * @attr ref android.R.styleable#TextView_elegantTextHeight
Raph Leviene272a262014-08-07 16:07:51 -0700237 * @attr ref android.R.styleable#TextView_letterSpacing
238 * @attr ref android.R.styleable#TextView_fontFeatureSettings
Raph Leviene319d5a2015-04-14 23:51:07 -0700239 * @attr ref android.R.styleable#TextView_breakStrategy
240 * @attr ref android.R.styleable#TextView_leftIndents
241 * @attr ref android.R.styleable#TextView_rightIndents
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 */
243@RemoteView
244public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700245 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800247
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 // Enum for the "typeface" XML parameter.
249 // TODO: How can we get this from the XML instead of hardcoding it here?
250 private static final int SANS = 1;
251 private static final int SERIF = 2;
252 private static final int MONOSPACE = 3;
253
254 // Bitfield for the "numeric" XML parameter.
255 // TODO: How can we get this from the XML instead of hardcoding it here?
256 private static final int SIGNED = 2;
257 private static final int DECIMAL = 4;
258
Adam Powell282e3772011-08-30 16:51:11 -0700259 /**
260 * Draw marquee text with fading edges as usual
261 */
262 private static final int MARQUEE_FADE_NORMAL = 0;
263
264 /**
265 * Draw marquee text as ellipsize end while inactive instead of with the fade.
266 * (Useful for devices where the fade can be expensive if overdone)
267 */
268 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
269
270 /**
271 * Draw marquee text with fading edges because it is currently active/animating.
272 */
273 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
274
Gilles Debunne60e21862012-01-30 15:04:14 -0800275 private static final int LINES = 1;
276 private static final int EMS = LINES;
277 private static final int PIXELS = 2;
278
279 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800280
281 // XXX should be much larger
282 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800283 private static final int ANIMATED_SCROLL_GAP = 250;
284
285 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
286 private static final Spanned EMPTY_SPANNED = new SpannedString("");
287
Gilles Debunne60e21862012-01-30 15:04:14 -0800288 private static final int CHANGE_WATCHER_PRIORITY = 100;
289
290 // New state used to change background based on whether this TextView is multiline.
291 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
292
293 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700294 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800295
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +0000296 /**
297 * @hide
298 */
299 static final int PROCESS_TEXT_REQUEST_CODE = 100;
300
Gilles Debunne60e21862012-01-30 15:04:14 -0800301 private ColorStateList mTextColor;
302 private ColorStateList mHintTextColor;
303 private ColorStateList mLinkTextColor;
Jon Mirandaa25dc4282014-07-01 14:35:07 -0700304 @ViewDebug.ExportedProperty(category = "text")
Gilles Debunne60e21862012-01-30 15:04:14 -0800305 private int mCurTextColor;
306 private int mCurHintTextColor;
307 private boolean mFreezesText;
Gilles Debunne60e21862012-01-30 15:04:14 -0800308 private boolean mDispatchTemporaryDetach;
309
Alan Viverettedf689992014-11-13 12:59:37 -0800310 /** Whether this view is temporarily detached from the parent view. */
311 boolean mTemporaryDetach;
312
Gilles Debunne60e21862012-01-30 15:04:14 -0800313 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
314 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
315
316 private float mShadowRadius, mShadowDx, mShadowDy;
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -0400317 private int mShadowColor;
318
Gilles Debunne60e21862012-01-30 15:04:14 -0800319 private boolean mPreDrawRegistered;
Craig Stoutf209ef92014-07-02 15:23:16 -0700320 private boolean mPreDrawListenerDetached;
Gilles Debunne60e21862012-01-30 15:04:14 -0800321
Michael Wright3a7e4832013-02-11 15:55:50 -0800322 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
323 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
324 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
325 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
326 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
327 private boolean mPreventDefaultMovement;
328
Gilles Debunne60e21862012-01-30 15:04:14 -0800329 private TextUtils.TruncateAt mEllipsize;
330
331 static class Drawables {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800332 static final int LEFT = 0;
333 static final int TOP = 1;
334 static final int RIGHT = 2;
335 static final int BOTTOM = 3;
336
337 static final int DRAWABLE_NONE = -1;
338 static final int DRAWABLE_RIGHT = 0;
339 static final int DRAWABLE_LEFT = 1;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800340
Gilles Debunne60e21862012-01-30 15:04:14 -0800341 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800342
Alan Viveretteb97d6982015-01-07 16:16:20 -0800343 final Drawable[] mShowing = new Drawable[4];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800344
Alan Viveretteb97d6982015-01-07 16:16:20 -0800345 ColorStateList mTintList;
346 PorterDuff.Mode mTintMode;
347 boolean mHasTint;
348 boolean mHasTintMode;
349
350 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700351 Drawable mDrawableLeftInitial, mDrawableRightInitial;
Alan Viveretteb97d6982015-01-07 16:16:20 -0800352
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700353 boolean mIsRtlCompatibilityMode;
354 boolean mOverride;
355
Gilles Debunne60e21862012-01-30 15:04:14 -0800356 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800357 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
358
Gilles Debunne60e21862012-01-30 15:04:14 -0800359 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800360 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
361
Gilles Debunne60e21862012-01-30 15:04:14 -0800362 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800363
364 int mDrawableSaved = DRAWABLE_NONE;
365
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700366 public Drawables(Context context) {
367 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
368 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
369 !context.getApplicationInfo().hasRtlSupport());
370 mOverride = false;
371 }
372
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800373 public void resolveWithLayoutDirection(int layoutDirection) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700374 // First reset "left" and "right" drawables to their initial values
Alan Viveretteb97d6982015-01-07 16:16:20 -0800375 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
376 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800377
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700378 if (mIsRtlCompatibilityMode) {
379 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
Alan Viveretteb97d6982015-01-07 16:16:20 -0800380 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
381 mShowing[Drawables.LEFT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700382 mDrawableSizeLeft = mDrawableSizeStart;
383 mDrawableHeightLeft = mDrawableHeightStart;
384 }
385 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
Alan Viveretteb97d6982015-01-07 16:16:20 -0800386 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
387 mShowing[Drawables.RIGHT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700388 mDrawableSizeRight = mDrawableSizeEnd;
389 mDrawableHeightRight = mDrawableHeightEnd;
390 }
391 } else {
392 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
393 // drawable if and only if they have been defined
394 switch(layoutDirection) {
395 case LAYOUT_DIRECTION_RTL:
396 if (mOverride) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800397 mShowing[Drawables.RIGHT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700398 mDrawableSizeRight = mDrawableSizeStart;
399 mDrawableHeightRight = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800400
Alan Viveretteb97d6982015-01-07 16:16:20 -0800401 mShowing[Drawables.LEFT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700402 mDrawableSizeLeft = mDrawableSizeEnd;
403 mDrawableHeightLeft = mDrawableHeightEnd;
404 }
405 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800406
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700407 case LAYOUT_DIRECTION_LTR:
408 default:
409 if (mOverride) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800410 mShowing[Drawables.LEFT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700411 mDrawableSizeLeft = mDrawableSizeStart;
412 mDrawableHeightLeft = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800413
Alan Viveretteb97d6982015-01-07 16:16:20 -0800414 mShowing[Drawables.RIGHT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700415 mDrawableSizeRight = mDrawableSizeEnd;
416 mDrawableHeightRight = mDrawableHeightEnd;
417 }
418 break;
419 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800420 }
421 applyErrorDrawableIfNeeded(layoutDirection);
422 updateDrawablesLayoutDirection(layoutDirection);
423 }
424
425 private void updateDrawablesLayoutDirection(int layoutDirection) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800426 for (Drawable dr : mShowing) {
427 if (dr != null) {
428 dr.setLayoutDirection(layoutDirection);
429 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800430 }
431 }
432
433 public void setErrorDrawable(Drawable dr, TextView tv) {
434 if (mDrawableError != dr && mDrawableError != null) {
435 mDrawableError.setCallback(null);
436 }
437 mDrawableError = dr;
438
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800439 if (mDrawableError != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800440 final Rect compoundRect = mCompoundRect;
441 final int[] state = tv.getDrawableState();
442
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800443 mDrawableError.setState(state);
444 mDrawableError.copyBounds(compoundRect);
445 mDrawableError.setCallback(tv);
446 mDrawableSizeError = compoundRect.width();
447 mDrawableHeightError = compoundRect.height();
448 } else {
449 mDrawableSizeError = mDrawableHeightError = 0;
450 }
451 }
452
453 private void applyErrorDrawableIfNeeded(int layoutDirection) {
454 // first restore the initial state if needed
455 switch (mDrawableSaved) {
456 case DRAWABLE_LEFT:
Alan Viveretteb97d6982015-01-07 16:16:20 -0800457 mShowing[Drawables.LEFT] = mDrawableTemp;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800458 mDrawableSizeLeft = mDrawableSizeTemp;
459 mDrawableHeightLeft = mDrawableHeightTemp;
460 break;
461 case DRAWABLE_RIGHT:
Alan Viveretteb97d6982015-01-07 16:16:20 -0800462 mShowing[Drawables.RIGHT] = mDrawableTemp;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800463 mDrawableSizeRight = mDrawableSizeTemp;
464 mDrawableHeightRight = mDrawableHeightTemp;
465 break;
466 case DRAWABLE_NONE:
467 default:
468 }
469 // then, if needed, assign the Error drawable to the correct location
470 if (mDrawableError != null) {
471 switch(layoutDirection) {
472 case LAYOUT_DIRECTION_RTL:
473 mDrawableSaved = DRAWABLE_LEFT;
474
Alan Viveretteb97d6982015-01-07 16:16:20 -0800475 mDrawableTemp = mShowing[Drawables.LEFT];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800476 mDrawableSizeTemp = mDrawableSizeLeft;
477 mDrawableHeightTemp = mDrawableHeightLeft;
478
Alan Viveretteb97d6982015-01-07 16:16:20 -0800479 mShowing[Drawables.LEFT] = mDrawableError;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800480 mDrawableSizeLeft = mDrawableSizeError;
481 mDrawableHeightLeft = mDrawableHeightError;
482 break;
483 case LAYOUT_DIRECTION_LTR:
484 default:
485 mDrawableSaved = DRAWABLE_RIGHT;
486
Alan Viveretteb97d6982015-01-07 16:16:20 -0800487 mDrawableTemp = mShowing[Drawables.RIGHT];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800488 mDrawableSizeTemp = mDrawableSizeRight;
489 mDrawableHeightTemp = mDrawableHeightRight;
490
Alan Viveretteb97d6982015-01-07 16:16:20 -0800491 mShowing[Drawables.RIGHT] = mDrawableError;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800492 mDrawableSizeRight = mDrawableSizeError;
493 mDrawableHeightRight = mDrawableHeightError;
494 break;
495 }
496 }
497 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800498 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800499
Gilles Debunned88876a2012-03-16 17:34:04 -0700500 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800501
502 private CharWrapper mCharWrapper;
503
504 private Marquee mMarquee;
505 private boolean mRestartMarquee;
506
507 private int mMarqueeRepeatLimit = 3;
508
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700509 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800510
511 /**
512 * On some devices the fading edges add a performance penalty if used
513 * extensively in the same layout. This mode indicates how the marquee
514 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
515 */
516 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
517
518 /**
519 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
520 * the layout that should be used when the mode switches.
521 */
522 private Layout mSavedMarqueeModeLayout;
523
524 @ViewDebug.ExportedProperty(category = "text")
525 private CharSequence mText;
526 private CharSequence mTransformed;
527 private BufferType mBufferType = BufferType.NORMAL;
528
529 private CharSequence mHint;
530 private Layout mHintLayout;
531
532 private MovementMethod mMovement;
533
534 private TransformationMethod mTransformation;
535 private boolean mAllowTransformationLengthChange;
536 private ChangeWatcher mChangeWatcher;
537
538 private ArrayList<TextWatcher> mListeners;
539
540 // display attributes
541 private final TextPaint mTextPaint;
542 private boolean mUserSetTextScaleX;
543 private Layout mLayout;
Raph Levien2e3aa442015-01-14 16:12:53 -0800544 private boolean mLocaleChanged = false;
Gilles Debunne60e21862012-01-30 15:04:14 -0800545
Alan Viverette816aa142015-04-10 15:41:10 -0700546 @ViewDebug.ExportedProperty(category = "text")
Gilles Debunne60e21862012-01-30 15:04:14 -0800547 private int mGravity = Gravity.TOP | Gravity.START;
548 private boolean mHorizontallyScrolling;
549
550 private int mAutoLinkMask;
551 private boolean mLinksClickable = true;
552
553 private float mSpacingMult = 1.0f;
554 private float mSpacingAdd = 0.0f;
555
Raph Levien39b4db72015-03-25 13:18:20 -0700556 private int mBreakStrategy;
Raph Leviene319d5a2015-04-14 23:51:07 -0700557 private int[] mLeftIndents;
558 private int[] mRightIndents;
Raph Levien39b4db72015-03-25 13:18:20 -0700559
Gilles Debunne60e21862012-01-30 15:04:14 -0800560 private int mMaximum = Integer.MAX_VALUE;
561 private int mMaxMode = LINES;
562 private int mMinimum = 0;
563 private int mMinMode = LINES;
564
565 private int mOldMaximum = mMaximum;
566 private int mOldMaxMode = mMaxMode;
567
568 private int mMaxWidth = Integer.MAX_VALUE;
569 private int mMaxWidthMode = PIXELS;
570 private int mMinWidth = 0;
571 private int mMinWidthMode = PIXELS;
572
573 private boolean mSingleLine;
574 private int mDesiredHeightAtMeasure = -1;
575 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700576 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800577
578 // tmp primitives, so we don't alloc them on each draw
579 private Rect mTempRect;
580 private long mLastScroll;
581 private Scroller mScroller;
582
583 private BoringLayout.Metrics mBoring, mHintBoring;
584 private BoringLayout mSavedLayout, mSavedHintLayout;
585
586 private TextDirectionHeuristic mTextDir;
587
588 private InputFilter[] mFilters = NO_FILTERS;
589
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900590 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900591
Gilles Debunne83051b82012-02-24 20:01:13 -0800592 // It is possible to have a selection even when mEditor is null (programmatically set, like when
593 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700594 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800595 private Path mHighlightPath;
596 private final Paint mHighlightPaint;
597 private boolean mHighlightPathBogus = true;
598
Gilles Debunne60e21862012-01-30 15:04:14 -0800599 // Although these fields are specific to editable text, they are not added to Editor because
600 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700601 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800602 // These four fields, could be moved to Editor, since we know their default values and we
603 // could condition the creation of the Editor to a non standard value. This is however
604 // brittle since the hardcoded values here (such as
605 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
606 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700607 int mTextSelectHandleLeftRes;
608 int mTextSelectHandleRightRes;
609 int mTextSelectHandleRes;
610 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800611
612 /**
613 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700614 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800615 */
616 private Editor mEditor;
617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 /*
619 * Kick-start the font cache for the zygote process (to pay the cost of
620 * initializing freetype for our default font only once).
621 */
622 static {
623 Paint p = new Paint();
624 p.setAntiAlias(true);
625 // We don't care about the result, just the side-effect of measuring.
626 p.measureText("H");
627 }
628
629 /**
630 * Interface definition for a callback to be invoked when an action is
631 * performed on the editor.
632 */
633 public interface OnEditorActionListener {
634 /**
635 * Called when an action is being performed.
636 *
637 * @param v The view that was clicked.
638 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700639 * identifier you supplied, or {@link EditorInfo#IME_NULL
640 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 * being pressed.
642 * @param event If triggered by an enter key, this is the event;
643 * otherwise, this is null.
644 * @return Return true if you have consumed the action, else false.
645 */
646 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
647 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 public TextView(Context context) {
650 this(context, null);
651 }
652
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800653 public TextView(Context context, @Nullable AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 this(context, attrs, com.android.internal.R.attr.textViewStyle);
655 }
656
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800657 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
Alan Viverette617feb92013-09-09 18:09:13 -0700658 this(context, attrs, defStyleAttr, 0);
659 }
660
Gilles Debunnee15b3582010-06-16 15:17:21 -0700661 @SuppressWarnings("deprecation")
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800662 public TextView(
663 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
Alan Viverette617feb92013-09-09 18:09:13 -0700664 super(context, attrs, defStyleAttr, defStyleRes);
665
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 mText = "";
667
Christopher Tate1373a8e2011-11-10 19:59:13 -0800668 final Resources res = getResources();
669 final CompatibilityInfo compat = res.getCompatibilityInfo();
670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800672 mTextPaint.density = res.getDisplayMetrics().density;
673 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800674
Gilles Debunne83051b82012-02-24 20:01:13 -0800675 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
676 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
677
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800678 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800679
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680 mTransformation = null;
681
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800682 int textColorHighlight = 0;
683 ColorStateList textColor = null;
684 ColorStateList textColorHint = null;
685 ColorStateList textColorLink = null;
686 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700687 String fontFamily = null;
Raph Levien42b30242015-01-29 12:49:19 -0800688 boolean fontFamilyExplicit = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 int typefaceIndex = -1;
690 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700691 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800692 int shadowcolor = 0;
693 float dx = 0, dy = 0, r = 0;
Raph Levien53c00772014-04-14 14:11:02 -0700694 boolean elegant = false;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400695 float letterSpacing = 0;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400696 String fontFeatureSettings = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700697 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700699 final Resources.Theme theme = context.getTheme();
700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 /*
702 * Look the appearance up without checking first if it exists because
703 * almost every TextView has one and it greatly simplifies the logic
704 * to be able to parse the appearance first and then let specific tags
705 * for this View override it.
706 */
Alan Viverette617feb92013-09-09 18:09:13 -0700707 TypedArray a = theme.obtainStyledAttributes(attrs,
708 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800709 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700710 int ap = a.getResourceId(
711 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
712 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700714 appearance = theme.obtainStyledAttributes(
715 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800716 }
717 if (appearance != null) {
718 int n = appearance.getIndexCount();
719 for (int i = 0; i < n; i++) {
720 int attr = appearance.getIndex(i);
721
722 switch (attr) {
723 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
724 textColorHighlight = appearance.getColor(attr, textColorHighlight);
725 break;
726
727 case com.android.internal.R.styleable.TextAppearance_textColor:
728 textColor = appearance.getColorStateList(attr);
729 break;
730
731 case com.android.internal.R.styleable.TextAppearance_textColorHint:
732 textColorHint = appearance.getColorStateList(attr);
733 break;
734
735 case com.android.internal.R.styleable.TextAppearance_textColorLink:
736 textColorLink = appearance.getColorStateList(attr);
737 break;
738
739 case com.android.internal.R.styleable.TextAppearance_textSize:
740 textSize = appearance.getDimensionPixelSize(attr, textSize);
741 break;
742
743 case com.android.internal.R.styleable.TextAppearance_typeface:
744 typefaceIndex = appearance.getInt(attr, -1);
745 break;
746
Raph Leviend570e892012-05-09 11:45:34 -0700747 case com.android.internal.R.styleable.TextAppearance_fontFamily:
748 fontFamily = appearance.getString(attr);
749 break;
750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 case com.android.internal.R.styleable.TextAppearance_textStyle:
752 styleIndex = appearance.getInt(attr, -1);
753 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700754
755 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
756 allCaps = appearance.getBoolean(attr, false);
757 break;
Adam Powellac91df82013-02-14 13:48:47 -0800758
759 case com.android.internal.R.styleable.TextAppearance_shadowColor:
Raph Levien202d1ec2014-04-14 11:20:22 -0700760 shadowcolor = appearance.getInt(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800761 break;
762
763 case com.android.internal.R.styleable.TextAppearance_shadowDx:
Raph Levien202d1ec2014-04-14 11:20:22 -0700764 dx = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800765 break;
766
767 case com.android.internal.R.styleable.TextAppearance_shadowDy:
Raph Levien202d1ec2014-04-14 11:20:22 -0700768 dy = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800769 break;
770
771 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
Raph Levien202d1ec2014-04-14 11:20:22 -0700772 r = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800773 break;
Raph Levien53c00772014-04-14 14:11:02 -0700774
775 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
776 elegant = appearance.getBoolean(attr, false);
777 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400778
779 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
780 letterSpacing = appearance.getFloat(attr, 0);
781 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400782
783 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
784 fontFeatureSettings = appearance.getString(attr);
785 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 }
787 }
788
789 appearance.recycle();
790 }
791
792 boolean editable = getDefaultEditable();
793 CharSequence inputMethod = null;
794 int numeric = 0;
795 CharSequence digits = null;
796 boolean phone = false;
797 boolean autotext = false;
798 int autocap = -1;
799 int buffertype = 0;
800 boolean selectallonfocus = false;
801 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700802 drawableBottom = null, drawableStart = null, drawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -0800803 ColorStateList drawableTint = null;
804 PorterDuff.Mode drawableTintMode = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805 int drawablePadding = 0;
806 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700807 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 int maxlength = -1;
809 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700810 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800811 boolean password = false;
812 int inputType = EditorInfo.TYPE_NULL;
813
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700814 a = theme.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -0700815 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700816
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800817 int n = a.getIndexCount();
818 for (int i = 0; i < n; i++) {
819 int attr = a.getIndex(i);
820
821 switch (attr) {
822 case com.android.internal.R.styleable.TextView_editable:
823 editable = a.getBoolean(attr, editable);
824 break;
825
826 case com.android.internal.R.styleable.TextView_inputMethod:
827 inputMethod = a.getText(attr);
828 break;
829
830 case com.android.internal.R.styleable.TextView_numeric:
831 numeric = a.getInt(attr, numeric);
832 break;
833
834 case com.android.internal.R.styleable.TextView_digits:
835 digits = a.getText(attr);
836 break;
837
838 case com.android.internal.R.styleable.TextView_phoneNumber:
839 phone = a.getBoolean(attr, phone);
840 break;
841
842 case com.android.internal.R.styleable.TextView_autoText:
843 autotext = a.getBoolean(attr, autotext);
844 break;
845
846 case com.android.internal.R.styleable.TextView_capitalize:
847 autocap = a.getInt(attr, autocap);
848 break;
849
850 case com.android.internal.R.styleable.TextView_bufferType:
851 buffertype = a.getInt(attr, buffertype);
852 break;
853
854 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
855 selectallonfocus = a.getBoolean(attr, selectallonfocus);
856 break;
857
858 case com.android.internal.R.styleable.TextView_autoLink:
859 mAutoLinkMask = a.getInt(attr, 0);
860 break;
861
862 case com.android.internal.R.styleable.TextView_linksClickable:
863 mLinksClickable = a.getBoolean(attr, true);
864 break;
865
866 case com.android.internal.R.styleable.TextView_drawableLeft:
867 drawableLeft = a.getDrawable(attr);
868 break;
869
870 case com.android.internal.R.styleable.TextView_drawableTop:
871 drawableTop = a.getDrawable(attr);
872 break;
873
874 case com.android.internal.R.styleable.TextView_drawableRight:
875 drawableRight = a.getDrawable(attr);
876 break;
877
878 case com.android.internal.R.styleable.TextView_drawableBottom:
879 drawableBottom = a.getDrawable(attr);
880 break;
881
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700882 case com.android.internal.R.styleable.TextView_drawableStart:
883 drawableStart = a.getDrawable(attr);
884 break;
885
886 case com.android.internal.R.styleable.TextView_drawableEnd:
887 drawableEnd = a.getDrawable(attr);
888 break;
889
Alan Viveretteb97d6982015-01-07 16:16:20 -0800890 case com.android.internal.R.styleable.TextView_drawableTint:
891 drawableTint = a.getColorStateList(attr);
892 break;
893
894 case com.android.internal.R.styleable.TextView_drawableTintMode:
895 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
896 break;
897
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 case com.android.internal.R.styleable.TextView_drawablePadding:
899 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
900 break;
901
902 case com.android.internal.R.styleable.TextView_maxLines:
903 setMaxLines(a.getInt(attr, -1));
904 break;
905
906 case com.android.internal.R.styleable.TextView_maxHeight:
907 setMaxHeight(a.getDimensionPixelSize(attr, -1));
908 break;
909
910 case com.android.internal.R.styleable.TextView_lines:
911 setLines(a.getInt(attr, -1));
912 break;
913
914 case com.android.internal.R.styleable.TextView_height:
915 setHeight(a.getDimensionPixelSize(attr, -1));
916 break;
917
918 case com.android.internal.R.styleable.TextView_minLines:
919 setMinLines(a.getInt(attr, -1));
920 break;
921
922 case com.android.internal.R.styleable.TextView_minHeight:
923 setMinHeight(a.getDimensionPixelSize(attr, -1));
924 break;
925
926 case com.android.internal.R.styleable.TextView_maxEms:
927 setMaxEms(a.getInt(attr, -1));
928 break;
929
930 case com.android.internal.R.styleable.TextView_maxWidth:
931 setMaxWidth(a.getDimensionPixelSize(attr, -1));
932 break;
933
934 case com.android.internal.R.styleable.TextView_ems:
935 setEms(a.getInt(attr, -1));
936 break;
937
938 case com.android.internal.R.styleable.TextView_width:
939 setWidth(a.getDimensionPixelSize(attr, -1));
940 break;
941
942 case com.android.internal.R.styleable.TextView_minEms:
943 setMinEms(a.getInt(attr, -1));
944 break;
945
946 case com.android.internal.R.styleable.TextView_minWidth:
947 setMinWidth(a.getDimensionPixelSize(attr, -1));
948 break;
949
950 case com.android.internal.R.styleable.TextView_gravity:
951 setGravity(a.getInt(attr, -1));
952 break;
953
954 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700955 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800956 break;
957
958 case com.android.internal.R.styleable.TextView_text:
959 text = a.getText(attr);
960 break;
961
962 case com.android.internal.R.styleable.TextView_scrollHorizontally:
963 if (a.getBoolean(attr, false)) {
964 setHorizontallyScrolling(true);
965 }
966 break;
967
968 case com.android.internal.R.styleable.TextView_singleLine:
969 singleLine = a.getBoolean(attr, singleLine);
970 break;
971
972 case com.android.internal.R.styleable.TextView_ellipsize:
973 ellipsize = a.getInt(attr, ellipsize);
974 break;
975
976 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
977 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
978 break;
979
980 case com.android.internal.R.styleable.TextView_includeFontPadding:
981 if (!a.getBoolean(attr, true)) {
982 setIncludeFontPadding(false);
983 }
984 break;
985
986 case com.android.internal.R.styleable.TextView_cursorVisible:
987 if (!a.getBoolean(attr, true)) {
988 setCursorVisible(false);
989 }
990 break;
991
992 case com.android.internal.R.styleable.TextView_maxLength:
993 maxlength = a.getInt(attr, -1);
994 break;
995
996 case com.android.internal.R.styleable.TextView_textScaleX:
997 setTextScaleX(a.getFloat(attr, 1.0f));
998 break;
999
1000 case com.android.internal.R.styleable.TextView_freezesText:
1001 mFreezesText = a.getBoolean(attr, false);
1002 break;
1003
1004 case com.android.internal.R.styleable.TextView_shadowColor:
1005 shadowcolor = a.getInt(attr, 0);
1006 break;
1007
1008 case com.android.internal.R.styleable.TextView_shadowDx:
1009 dx = a.getFloat(attr, 0);
1010 break;
1011
1012 case com.android.internal.R.styleable.TextView_shadowDy:
1013 dy = a.getFloat(attr, 0);
1014 break;
1015
1016 case com.android.internal.R.styleable.TextView_shadowRadius:
1017 r = a.getFloat(attr, 0);
1018 break;
1019
1020 case com.android.internal.R.styleable.TextView_enabled:
1021 setEnabled(a.getBoolean(attr, isEnabled()));
1022 break;
1023
1024 case com.android.internal.R.styleable.TextView_textColorHighlight:
1025 textColorHighlight = a.getColor(attr, textColorHighlight);
1026 break;
1027
1028 case com.android.internal.R.styleable.TextView_textColor:
1029 textColor = a.getColorStateList(attr);
1030 break;
1031
1032 case com.android.internal.R.styleable.TextView_textColorHint:
1033 textColorHint = a.getColorStateList(attr);
1034 break;
1035
1036 case com.android.internal.R.styleable.TextView_textColorLink:
1037 textColorLink = a.getColorStateList(attr);
1038 break;
1039
1040 case com.android.internal.R.styleable.TextView_textSize:
1041 textSize = a.getDimensionPixelSize(attr, textSize);
1042 break;
1043
1044 case com.android.internal.R.styleable.TextView_typeface:
1045 typefaceIndex = a.getInt(attr, typefaceIndex);
1046 break;
1047
1048 case com.android.internal.R.styleable.TextView_textStyle:
1049 styleIndex = a.getInt(attr, styleIndex);
1050 break;
1051
Raph Leviend570e892012-05-09 11:45:34 -07001052 case com.android.internal.R.styleable.TextView_fontFamily:
1053 fontFamily = a.getString(attr);
Raph Levien42b30242015-01-29 12:49:19 -08001054 fontFamilyExplicit = true;
Raph Leviend570e892012-05-09 11:45:34 -07001055 break;
1056
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 case com.android.internal.R.styleable.TextView_password:
1058 password = a.getBoolean(attr, password);
1059 break;
1060
1061 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1062 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1063 break;
1064
1065 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1066 mSpacingMult = a.getFloat(attr, mSpacingMult);
1067 break;
1068
1069 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001070 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001071 break;
1072
James Cookf1dad1e2015-02-27 11:00:01 -08001073 case com.android.internal.R.styleable.TextView_allowUndo:
1074 createEditorIfNeeded();
1075 mEditor.mAllowUndo = a.getBoolean(attr, true);
1076 break;
1077
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001079 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001080 mEditor.createInputContentTypeIfNeeded();
1081 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1082 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 break;
1084
1085 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001086 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001087 mEditor.createInputContentTypeIfNeeded();
1088 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001089 break;
1090
1091 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001092 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001093 mEditor.createInputContentTypeIfNeeded();
1094 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1095 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001096 break;
1097
1098 case com.android.internal.R.styleable.TextView_privateImeOptions:
1099 setPrivateImeOptions(a.getString(attr));
1100 break;
1101
1102 case com.android.internal.R.styleable.TextView_editorExtras:
1103 try {
1104 setInputExtras(a.getResourceId(attr, 0));
1105 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001106 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001107 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001108 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 }
1110 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001111
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001112 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1113 mCursorDrawableRes = a.getResourceId(attr, 0);
1114 break;
1115
Adam Powellb08013c2010-09-16 16:28:11 -07001116 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1117 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1118 break;
1119
1120 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1121 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1122 break;
1123
1124 case com.android.internal.R.styleable.TextView_textSelectHandle:
1125 mTextSelectHandleRes = a.getResourceId(attr, 0);
1126 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001127
Gilles Debunne69340442011-03-31 13:37:51 -07001128 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1129 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1130 break;
1131
Gilles Debunne86b9c782010-11-11 10:43:48 -08001132 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001133 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001134 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001135
Adam Powell7f8f79a2011-07-07 18:35:54 -07001136 case com.android.internal.R.styleable.TextView_textAllCaps:
1137 allCaps = a.getBoolean(attr, false);
1138 break;
Raph Levien53c00772014-04-14 14:11:02 -07001139
1140 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1141 elegant = a.getBoolean(attr, false);
1142 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001143
1144 case com.android.internal.R.styleable.TextView_letterSpacing:
1145 letterSpacing = a.getFloat(attr, 0);
1146 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001147
1148 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1149 fontFeatureSettings = a.getString(attr);
1150 break;
Raph Levien39b4db72015-03-25 13:18:20 -07001151
1152 case com.android.internal.R.styleable.TextView_breakStrategy:
1153 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
Raph Leviene319d5a2015-04-14 23:51:07 -07001154 break;
1155
1156 case com.android.internal.R.styleable.TextView_leftIndents:
1157 TypedArray margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID));
1158 mLeftIndents = parseDimensionArray(margins);
1159 break;
1160
1161 case com.android.internal.R.styleable.TextView_rightIndents:
1162 margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID));
1163 mRightIndents = parseDimensionArray(margins);
1164 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001165 }
1166 }
1167 a.recycle();
1168
1169 BufferType bufferType = BufferType.EDITABLE;
1170
Gilles Debunned7483bf2010-11-10 10:47:45 -08001171 final int variation =
1172 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1173 final boolean passwordInputType = variation
1174 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1175 final boolean webPasswordInputType = variation
1176 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001177 final boolean numberPasswordInputType = variation
1178 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001179
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001180 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001181 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182
1183 try {
1184 c = Class.forName(inputMethod.toString());
1185 } catch (ClassNotFoundException ex) {
1186 throw new RuntimeException(ex);
1187 }
1188
1189 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001190 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001191 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 } catch (InstantiationException ex) {
1193 throw new RuntimeException(ex);
1194 } catch (IllegalAccessException ex) {
1195 throw new RuntimeException(ex);
1196 }
1197 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001198 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001200 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001202 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 }
1204 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001205 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001206 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001207 // If no input type was specified, we will default to generic
1208 // text, since we can't tell the IME about the set of digits
1209 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001210 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001211 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 } else if (inputType != EditorInfo.TYPE_NULL) {
1213 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001214 // If set, the input type overrides what was set using the deprecated singleLine flag.
1215 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001217 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001218 mEditor.mKeyListener = DialerKeyListener.getInstance();
1219 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001220 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001221 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001222 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001223 (numeric & DECIMAL) != 0);
1224 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1225 if ((numeric & SIGNED) != 0) {
1226 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1227 }
1228 if ((numeric & DECIMAL) != 0) {
1229 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1230 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001231 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 } else if (autotext || autocap != -1) {
1233 TextKeyListener.Capitalize cap;
1234
1235 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001237 switch (autocap) {
1238 case 1:
1239 cap = TextKeyListener.Capitalize.SENTENCES;
1240 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1241 break;
1242
1243 case 2:
1244 cap = TextKeyListener.Capitalize.WORDS;
1245 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1246 break;
1247
1248 case 3:
1249 cap = TextKeyListener.Capitalize.CHARACTERS;
1250 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1251 break;
1252
1253 default:
1254 cap = TextKeyListener.Capitalize.NONE;
1255 break;
1256 }
1257
Gilles Debunne5fae9962012-05-08 14:53:20 -07001258 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001259 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1260 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001261 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001262 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001263 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001264 mEditor.mKeyListener = null;
1265 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001266 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001267 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001268 // So that selection can be changed using arrow keys and touch is handled.
1269 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001271 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001272 mEditor.mKeyListener = TextKeyListener.getInstance();
1273 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001275 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001276
1277 switch (buffertype) {
1278 case 0:
1279 bufferType = BufferType.NORMAL;
1280 break;
1281 case 1:
1282 bufferType = BufferType.SPANNABLE;
1283 break;
1284 case 2:
1285 bufferType = BufferType.EDITABLE;
1286 break;
1287 }
1288 }
1289
Gilles Debunne2d373a12012-04-20 15:32:19 -07001290 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1291 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001292
1293 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001294 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001295 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001296
1297 if (bufferType == BufferType.NORMAL)
1298 bufferType = BufferType.SPANNABLE;
1299 }
1300
Alan Viveretteb97d6982015-01-07 16:16:20 -08001301 // Set up the tint (if needed) before setting the drawables so that it
1302 // gets applied correctly.
1303 if (drawableTint != null || drawableTintMode != null) {
1304 if (mDrawables == null) {
1305 mDrawables = new Drawables(context);
1306 }
1307 if (drawableTint != null) {
1308 mDrawables.mTintList = drawableTint;
1309 mDrawables.mHasTint = true;
1310 }
1311 if (drawableTintMode != null) {
1312 mDrawables.mTintMode = drawableTintMode;
1313 mDrawables.mHasTintMode = true;
1314 }
1315 }
1316
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001317 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001318 setCompoundDrawablesWithIntrinsicBounds(
1319 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001320 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 setCompoundDrawablePadding(drawablePadding);
1322
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001323 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001324 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001325 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001326 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001327
Gilles Debunne60e21862012-01-30 15:04:14 -08001328 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001329 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 }
1331
1332 switch (ellipsize) {
1333 case 1:
1334 setEllipsize(TextUtils.TruncateAt.START);
1335 break;
1336 case 2:
1337 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1338 break;
1339 case 3:
1340 setEllipsize(TextUtils.TruncateAt.END);
1341 break;
1342 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001343 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1344 setHorizontalFadingEdgeEnabled(true);
1345 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1346 } else {
1347 setHorizontalFadingEdgeEnabled(false);
1348 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001350 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1351 break;
1352 }
1353
1354 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1355 setHintTextColor(textColorHint);
1356 setLinkTextColor(textColorLink);
1357 if (textColorHighlight != 0) {
1358 setHighlightColor(textColorHighlight);
1359 }
1360 setRawTextSize(textSize);
Raph Levien53c00772014-04-14 14:11:02 -07001361 setElegantTextHeight(elegant);
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001362 setLetterSpacing(letterSpacing);
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001363 setFontFeatureSettings(fontFeatureSettings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001364
Adam Powell7f8f79a2011-07-07 18:35:54 -07001365 if (allCaps) {
1366 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1367 }
1368
Ken Wakasa82d731a2010-12-24 23:42:41 +09001369 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001370 setTransformationMethod(PasswordTransformationMethod.getInstance());
1371 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001372 } else if (mEditor != null &&
1373 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001374 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001375 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001376 }
1377
Raph Levien42b30242015-01-29 12:49:19 -08001378 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1379 fontFamily = null;
1380 }
Raph Leviend570e892012-05-09 11:45:34 -07001381 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001382
1383 if (shadowcolor != 0) {
1384 setShadowLayer(r, dx, dy, shadowcolor);
1385 }
1386
1387 if (maxlength >= 0) {
1388 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1389 } else {
1390 setFilters(NO_FILTERS);
1391 }
1392
1393 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001394 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001395
1396 /*
1397 * Views are not normally focusable unless specified to be.
1398 * However, TextViews that have input or movement methods *are*
1399 * focusable by default.
1400 */
Alan Viverette617feb92013-09-09 18:09:13 -07001401 a = context.obtainStyledAttributes(
1402 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001403
Gilles Debunne60e21862012-01-30 15:04:14 -08001404 boolean focusable = mMovement != null || getKeyListener() != null;
Alan Viverettef32efeb2014-08-14 14:03:21 -07001405 boolean clickable = focusable || isClickable();
1406 boolean longClickable = focusable || isLongClickable();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001407
1408 n = a.getIndexCount();
1409 for (int i = 0; i < n; i++) {
1410 int attr = a.getIndex(i);
1411
1412 switch (attr) {
1413 case com.android.internal.R.styleable.View_focusable:
1414 focusable = a.getBoolean(attr, focusable);
1415 break;
1416
1417 case com.android.internal.R.styleable.View_clickable:
1418 clickable = a.getBoolean(attr, clickable);
1419 break;
1420
1421 case com.android.internal.R.styleable.View_longClickable:
1422 longClickable = a.getBoolean(attr, longClickable);
1423 break;
1424 }
1425 }
1426 a.recycle();
1427
1428 setFocusable(focusable);
1429 setClickable(clickable);
1430 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001431
Gilles Debunned88876a2012-03-16 17:34:04 -07001432 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001433
1434 // If not explicitly specified this view is important for accessibility.
1435 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1436 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1437 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001438 }
1439
Raph Leviene319d5a2015-04-14 23:51:07 -07001440 private int[] parseDimensionArray(TypedArray dimens) {
1441 if (dimens == null) {
1442 return null;
1443 }
1444 int[] result = new int[dimens.length()];
1445 for (int i = 0; i < result.length; i++) {
1446 result[i] = dimens.getDimensionPixelSize(i, 0);
1447 }
1448 return result;
1449 }
1450
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001451 /**
1452 * @hide
1453 */
1454 @Override
1455 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1456 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1457 CharSequence result = data != null
1458 ? data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
1459 : "";
1460 if (isTextEditable()) {
1461 replaceSelectionWithText(result);
1462 } else {
Andrei Stingaceanu6154b092015-04-20 14:53:47 +01001463 if (result.length() > 0) {
1464 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG).show();
1465 }
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001466 }
1467 }
1468 }
1469
Raph Leviend570e892012-05-09 11:45:34 -07001470 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001472 if (familyName != null) {
1473 tf = Typeface.create(familyName, styleIndex);
1474 if (tf != null) {
1475 setTypeface(tf);
1476 return;
1477 }
1478 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001479 switch (typefaceIndex) {
1480 case SANS:
1481 tf = Typeface.SANS_SERIF;
1482 break;
1483
1484 case SERIF:
1485 tf = Typeface.SERIF;
1486 break;
1487
1488 case MONOSPACE:
1489 tf = Typeface.MONOSPACE;
1490 break;
1491 }
1492
1493 setTypeface(tf, styleIndex);
1494 }
1495
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001496 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1497 boolean hasRelativeDrawables = (start != null) || (end != null);
1498 if (hasRelativeDrawables) {
1499 Drawables dr = mDrawables;
1500 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001501 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001502 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001503 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001504 final Rect compoundRect = dr.mCompoundRect;
1505 int[] state = getDrawableState();
1506 if (start != null) {
1507 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1508 start.setState(state);
1509 start.copyBounds(compoundRect);
1510 start.setCallback(this);
1511
1512 dr.mDrawableStart = start;
1513 dr.mDrawableSizeStart = compoundRect.width();
1514 dr.mDrawableHeightStart = compoundRect.height();
1515 } else {
1516 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1517 }
1518 if (end != null) {
1519 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1520 end.setState(state);
1521 end.copyBounds(compoundRect);
1522 end.setCallback(this);
1523
1524 dr.mDrawableEnd = end;
1525 dr.mDrawableSizeEnd = compoundRect.width();
1526 dr.mDrawableHeightEnd = compoundRect.height();
1527 } else {
1528 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1529 }
Fabrice Di Meglio4155e2e2013-08-08 16:28:07 -07001530 resetResolvedDrawables();
1531 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08001532 applyCompoundDrawableTint();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001533 }
1534 }
1535
Janos Levai042856c2010-10-15 02:53:58 +03001536 @Override
1537 public void setEnabled(boolean enabled) {
1538 if (enabled == isEnabled()) {
1539 return;
1540 }
1541
1542 if (!enabled) {
1543 // Hide the soft input if the currently active TextView is disabled
1544 InputMethodManager imm = InputMethodManager.peekInstance();
1545 if (imm != null && imm.isActive(this)) {
1546 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1547 }
1548 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001549
Janos Levai042856c2010-10-15 02:53:58 +03001550 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001551
Dianne Hackbornbc823852011-09-18 17:19:50 -07001552 if (enabled) {
1553 // Make sure IME is updated with current editor info.
1554 InputMethodManager imm = InputMethodManager.peekInstance();
1555 if (imm != null) imm.restartInput(this);
1556 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001557
Gilles Debunne33b7de852012-03-12 11:57:48 -07001558 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001559 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001560 mEditor.invalidateTextDisplayList();
1561 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001562
Gilles Debunned88876a2012-03-16 17:34:04 -07001563 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001564 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001565 }
Janos Levai042856c2010-10-15 02:53:58 +03001566 }
1567
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001568 /**
1569 * Sets the typeface and style in which the text should be displayed,
1570 * and turns on the fake bold and italic bits in the Paint if the
1571 * Typeface that you provided does not have all the bits in the
1572 * style that you specified.
1573 *
1574 * @attr ref android.R.styleable#TextView_typeface
1575 * @attr ref android.R.styleable#TextView_textStyle
1576 */
1577 public void setTypeface(Typeface tf, int style) {
1578 if (style > 0) {
1579 if (tf == null) {
1580 tf = Typeface.defaultFromStyle(style);
1581 } else {
1582 tf = Typeface.create(tf, style);
1583 }
1584
1585 setTypeface(tf);
1586 // now compute what (if any) algorithmic styling is needed
1587 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1588 int need = style & ~typefaceStyle;
1589 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1590 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1591 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001592 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001593 mTextPaint.setTextSkewX(0);
1594 setTypeface(tf);
1595 }
1596 }
1597
1598 /**
1599 * Subclasses override this to specify that they have a KeyListener
1600 * by default even if not specifically called for in the XML options.
1601 */
1602 protected boolean getDefaultEditable() {
1603 return false;
1604 }
1605
1606 /**
1607 * Subclasses override this to specify a default movement method.
1608 */
1609 protected MovementMethod getDefaultMovementMethod() {
1610 return null;
1611 }
1612
1613 /**
1614 * Return the text the TextView is displaying. If setText() was called with
1615 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1616 * the return value from this method to Spannable or Editable, respectively.
1617 *
1618 * Note: The content of the return value should not be modified. If you want
1619 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001620 *
1621 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001622 */
1623 @ViewDebug.CapturedViewProperty
1624 public CharSequence getText() {
1625 return mText;
1626 }
1627
1628 /**
1629 * Returns the length, in characters, of the text managed by this TextView
1630 */
1631 public int length() {
1632 return mText.length();
1633 }
1634
1635 /**
1636 * Return the text the TextView is displaying as an Editable object. If
1637 * the text is not editable, null is returned.
1638 *
1639 * @see #getText
1640 */
1641 public Editable getEditableText() {
1642 return (mText instanceof Editable) ? (Editable)mText : null;
1643 }
1644
1645 /**
1646 * @return the height of one standard line in pixels. Note that markup
1647 * within the text can cause individual lines to be taller or shorter
1648 * than this height, and the layout may contain additional first-
1649 * or last-line padding.
1650 */
1651 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001652 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001653 }
1654
1655 /**
1656 * @return the Layout that is currently being used to display the text.
1657 * This can be null if the text or width has recently changes.
1658 */
1659 public final Layout getLayout() {
1660 return mLayout;
1661 }
1662
1663 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001664 * @return the Layout that is currently being used to display the hint text.
1665 * This can be null.
1666 */
1667 final Layout getHintLayout() {
1668 return mHintLayout;
1669 }
1670
1671 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001672 * Retrieve the {@link android.content.UndoManager} that is currently associated
1673 * with this TextView. By default there is no associated UndoManager, so null
1674 * is returned. One can be associated with the TextView through
1675 * {@link #setUndoManager(android.content.UndoManager, String)}
Dianne Hackbornb811e642013-09-04 17:43:56 -07001676 *
1677 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001678 */
1679 public final UndoManager getUndoManager() {
James Cookf59152c2015-02-26 18:03:58 -08001680 // TODO: Consider supporting a global undo manager.
1681 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001682 }
1683
1684 /**
1685 * Associate an {@link android.content.UndoManager} with this TextView. Once
1686 * done, all edit operations on the TextView will result in appropriate
1687 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1688 * stack.
1689 *
1690 * @param undoManager The {@link android.content.UndoManager} to associate with
1691 * this TextView, or null to clear any existing association.
1692 * @param tag String tag identifying this particular TextView owner in the
1693 * UndoManager. This is used to keep the correct association with the
1694 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
Dianne Hackbornb811e642013-09-04 17:43:56 -07001695 *
1696 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001697 */
1698 public final void setUndoManager(UndoManager undoManager, String tag) {
James Cookf59152c2015-02-26 18:03:58 -08001699 // TODO: Consider supporting a global undo manager. An implementation will need to:
1700 // * createEditorIfNeeded()
1701 // * Promote to BufferType.EDITABLE if needed.
1702 // * Update the UndoManager and UndoOwner.
1703 // Likewise it will need to be able to restore the default UndoManager.
1704 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001705 }
1706
1707 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001708 * @return the current key listener for this TextView.
1709 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001710 *
1711 * @attr ref android.R.styleable#TextView_numeric
1712 * @attr ref android.R.styleable#TextView_digits
1713 * @attr ref android.R.styleable#TextView_phoneNumber
1714 * @attr ref android.R.styleable#TextView_inputMethod
1715 * @attr ref android.R.styleable#TextView_capitalize
1716 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001717 */
1718 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001719 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001720 }
1721
1722 /**
1723 * Sets the key listener to be used with this TextView. This can be null
1724 * to disallow user input. Note that this method has significant and
1725 * subtle interactions with soft keyboards and other input method:
1726 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1727 * for important details. Calling this method will replace the current
1728 * content type of the text view with the content type returned by the
1729 * key listener.
1730 * <p>
1731 * Be warned that if you want a TextView with a key listener or movement
1732 * method not to be focusable, or if you want a TextView without a
1733 * key listener or movement method to be focusable, you must call
1734 * {@link #setFocusable} again after calling this to get the focusability
1735 * back the way you want it.
1736 *
1737 * @attr ref android.R.styleable#TextView_numeric
1738 * @attr ref android.R.styleable#TextView_digits
1739 * @attr ref android.R.styleable#TextView_phoneNumber
1740 * @attr ref android.R.styleable#TextView_inputMethod
1741 * @attr ref android.R.styleable#TextView_capitalize
1742 * @attr ref android.R.styleable#TextView_autoText
1743 */
1744 public void setKeyListener(KeyListener input) {
1745 setKeyListenerOnly(input);
1746 fixFocusableAndClickableSettings();
1747
1748 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001749 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001750 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001751 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001752 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001753 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001754 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001755 // Change inputType, without affecting transformation.
1756 // No need to applySingleLine since mSingleLine is unchanged.
1757 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001758 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001759 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001760 }
1761
1762 InputMethodManager imm = InputMethodManager.peekInstance();
1763 if (imm != null) imm.restartInput(this);
1764 }
1765
1766 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001767 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001768
Gilles Debunne5fae9962012-05-08 14:53:20 -07001769 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001770 if (mEditor.mKeyListener != input) {
1771 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001772 if (input != null && !(mText instanceof Editable)) {
1773 setText(mText);
1774 }
1775
1776 setFilters((Editable) mText, mFilters);
1777 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 }
1779
1780 /**
1781 * @return the movement method being used for this TextView.
1782 * This will frequently be null for non-EditText TextViews.
1783 */
1784 public final MovementMethod getMovementMethod() {
1785 return mMovement;
1786 }
1787
1788 /**
1789 * Sets the movement method (arrow key handler) to be used for
1790 * this TextView. This can be null to disallow using the arrow keys
1791 * to move the cursor or scroll the view.
1792 * <p>
1793 * Be warned that if you want a TextView with a key listener or movement
1794 * method not to be focusable, or if you want a TextView without a
1795 * key listener or movement method to be focusable, you must call
1796 * {@link #setFocusable} again after calling this to get the focusability
1797 * back the way you want it.
1798 */
1799 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001800 if (mMovement != movement) {
1801 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001802
Gilles Debunne60e21862012-01-30 15:04:14 -08001803 if (movement != null && !(mText instanceof Spannable)) {
1804 setText(mText);
1805 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001806
Gilles Debunne60e21862012-01-30 15:04:14 -08001807 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001808
Gilles Debunne2d373a12012-04-20 15:32:19 -07001809 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1810 // mMovement
1811 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001812 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001813 }
1814
1815 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001816 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001817 setFocusable(true);
1818 setClickable(true);
1819 setLongClickable(true);
1820 } else {
1821 setFocusable(false);
1822 setClickable(false);
1823 setLongClickable(false);
1824 }
1825 }
1826
1827 /**
1828 * @return the current transformation method for this TextView.
1829 * This will frequently be null except for single-line and password
1830 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001831 *
1832 * @attr ref android.R.styleable#TextView_password
1833 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 */
1835 public final TransformationMethod getTransformationMethod() {
1836 return mTransformation;
1837 }
1838
1839 /**
1840 * Sets the transformation that is applied to the text that this
1841 * TextView is displaying.
1842 *
1843 * @attr ref android.R.styleable#TextView_password
1844 * @attr ref android.R.styleable#TextView_singleLine
1845 */
1846 public final void setTransformationMethod(TransformationMethod method) {
1847 if (method == mTransformation) {
1848 // Avoid the setText() below if the transformation is
1849 // the same.
1850 return;
1851 }
1852 if (mTransformation != null) {
1853 if (mText instanceof Spannable) {
1854 ((Spannable) mText).removeSpan(mTransformation);
1855 }
1856 }
1857
1858 mTransformation = method;
1859
Adam Powell7f8f79a2011-07-07 18:35:54 -07001860 if (method instanceof TransformationMethod2) {
1861 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001862 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001863 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1864 } else {
1865 mAllowTransformationLengthChange = false;
1866 }
1867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001868 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001869
1870 if (hasPasswordTransformationMethod()) {
Alan Viverette77e9a282013-09-12 17:16:09 -07001871 notifyViewAccessibilityStateChangedIfNeeded(
1872 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001873 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001874 }
1875
1876 /**
1877 * Returns the top padding of the view, plus space for the top
1878 * Drawable if any.
1879 */
1880 public int getCompoundPaddingTop() {
1881 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001882 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001883 return mPaddingTop;
1884 } else {
1885 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1886 }
1887 }
1888
1889 /**
1890 * Returns the bottom padding of the view, plus space for the bottom
1891 * Drawable if any.
1892 */
1893 public int getCompoundPaddingBottom() {
1894 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001895 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001896 return mPaddingBottom;
1897 } else {
1898 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1899 }
1900 }
1901
1902 /**
1903 * Returns the left padding of the view, plus space for the left
1904 * Drawable if any.
1905 */
1906 public int getCompoundPaddingLeft() {
1907 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001908 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 return mPaddingLeft;
1910 } else {
1911 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1912 }
1913 }
1914
1915 /**
1916 * Returns the right padding of the view, plus space for the right
1917 * Drawable if any.
1918 */
1919 public int getCompoundPaddingRight() {
1920 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001921 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001922 return mPaddingRight;
1923 } else {
1924 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1925 }
1926 }
1927
1928 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001929 * Returns the start padding of the view, plus space for the start
1930 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001931 */
1932 public int getCompoundPaddingStart() {
1933 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001934 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001935 default:
1936 case LAYOUT_DIRECTION_LTR:
1937 return getCompoundPaddingLeft();
1938 case LAYOUT_DIRECTION_RTL:
1939 return getCompoundPaddingRight();
1940 }
1941 }
1942
1943 /**
1944 * Returns the end padding of the view, plus space for the end
1945 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001946 */
1947 public int getCompoundPaddingEnd() {
1948 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001949 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001950 default:
1951 case LAYOUT_DIRECTION_LTR:
1952 return getCompoundPaddingRight();
1953 case LAYOUT_DIRECTION_RTL:
1954 return getCompoundPaddingLeft();
1955 }
1956 }
1957
1958 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001959 * Returns the extended top padding of the view, including both the
1960 * top Drawable if any and any extra space to keep more than maxLines
1961 * of text from showing. It is only valid to call this after measuring.
1962 */
1963 public int getExtendedPaddingTop() {
1964 if (mMaxMode != LINES) {
1965 return getCompoundPaddingTop();
1966 }
1967
Raph Levien463cf1a2014-09-02 14:22:23 -07001968 if (mLayout == null) {
1969 assumeLayout();
1970 }
1971
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001972 if (mLayout.getLineCount() <= mMaximum) {
1973 return getCompoundPaddingTop();
1974 }
1975
1976 int top = getCompoundPaddingTop();
1977 int bottom = getCompoundPaddingBottom();
1978 int viewht = getHeight() - top - bottom;
1979 int layoutht = mLayout.getLineTop(mMaximum);
1980
1981 if (layoutht >= viewht) {
1982 return top;
1983 }
1984
1985 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1986 if (gravity == Gravity.TOP) {
1987 return top;
1988 } else if (gravity == Gravity.BOTTOM) {
1989 return top + viewht - layoutht;
1990 } else { // (gravity == Gravity.CENTER_VERTICAL)
1991 return top + (viewht - layoutht) / 2;
1992 }
1993 }
1994
1995 /**
1996 * Returns the extended bottom padding of the view, including both the
1997 * bottom Drawable if any and any extra space to keep more than maxLines
1998 * of text from showing. It is only valid to call this after measuring.
1999 */
2000 public int getExtendedPaddingBottom() {
2001 if (mMaxMode != LINES) {
2002 return getCompoundPaddingBottom();
2003 }
2004
Raph Levien463cf1a2014-09-02 14:22:23 -07002005 if (mLayout == null) {
2006 assumeLayout();
2007 }
2008
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002009 if (mLayout.getLineCount() <= mMaximum) {
2010 return getCompoundPaddingBottom();
2011 }
2012
2013 int top = getCompoundPaddingTop();
2014 int bottom = getCompoundPaddingBottom();
2015 int viewht = getHeight() - top - bottom;
2016 int layoutht = mLayout.getLineTop(mMaximum);
2017
2018 if (layoutht >= viewht) {
2019 return bottom;
2020 }
2021
2022 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2023 if (gravity == Gravity.TOP) {
2024 return bottom + viewht - layoutht;
2025 } else if (gravity == Gravity.BOTTOM) {
2026 return bottom;
2027 } else { // (gravity == Gravity.CENTER_VERTICAL)
2028 return bottom + (viewht - layoutht) / 2;
2029 }
2030 }
2031
2032 /**
2033 * Returns the total left padding of the view, including the left
2034 * Drawable if any.
2035 */
2036 public int getTotalPaddingLeft() {
2037 return getCompoundPaddingLeft();
2038 }
2039
2040 /**
2041 * Returns the total right padding of the view, including the right
2042 * Drawable if any.
2043 */
2044 public int getTotalPaddingRight() {
2045 return getCompoundPaddingRight();
2046 }
2047
2048 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002049 * Returns the total start padding of the view, including the start
2050 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002051 */
2052 public int getTotalPaddingStart() {
2053 return getCompoundPaddingStart();
2054 }
2055
2056 /**
2057 * Returns the total end padding of the view, including the end
2058 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002059 */
2060 public int getTotalPaddingEnd() {
2061 return getCompoundPaddingEnd();
2062 }
2063
2064 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002065 * Returns the total top padding of the view, including the top
2066 * Drawable if any, the extra space to keep more than maxLines
2067 * from showing, and the vertical offset for gravity, if any.
2068 */
2069 public int getTotalPaddingTop() {
2070 return getExtendedPaddingTop() + getVerticalOffset(true);
2071 }
2072
2073 /**
2074 * Returns the total bottom padding of the view, including the bottom
2075 * Drawable if any, the extra space to keep more than maxLines
2076 * from showing, and the vertical offset for gravity, if any.
2077 */
2078 public int getTotalPaddingBottom() {
2079 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2080 }
2081
2082 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002083 * Sets the Drawables (if any) to appear to the left of, above, to the
2084 * right of, and below the text. Use {@code null} if you do not want a
2085 * Drawable there. The Drawables must already have had
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002086 * {@link Drawable#setBounds} called.
Alan Viverette97f84ee2014-09-09 16:55:56 -07002087 * <p>
2088 * Calling this method will overwrite any Drawables previously set using
2089 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002090 *
2091 * @attr ref android.R.styleable#TextView_drawableLeft
2092 * @attr ref android.R.styleable#TextView_drawableTop
2093 * @attr ref android.R.styleable#TextView_drawableRight
2094 * @attr ref android.R.styleable#TextView_drawableBottom
2095 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002096 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2097 @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002098 Drawables dr = mDrawables;
2099
Alan Viverette97f84ee2014-09-09 16:55:56 -07002100 // We're switching to absolute, discard relative.
2101 if (dr != null) {
2102 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2103 dr.mDrawableStart = null;
2104 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2105 dr.mDrawableEnd = null;
2106 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2107 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002109
Alan Viverette97f84ee2014-09-09 16:55:56 -07002110 final boolean drawables = left != null || top != null || right != null || bottom != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002111 if (!drawables) {
2112 // Clearing drawables... can we free the data structure?
2113 if (dr != null) {
2114 if (dr.mDrawablePadding == 0) {
2115 mDrawables = null;
2116 } else {
2117 // We need to retain the last set padding, so just clear
2118 // out all of the fields in the existing structure.
Alan Viveretteb97d6982015-01-07 16:16:20 -08002119 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2120 if (dr.mShowing[i] != null) {
2121 dr.mShowing[i].setCallback(null);
2122 }
2123 dr.mShowing[i] = null;
2124 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002125 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2126 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2127 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2128 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2129 }
2130 }
2131 } else {
2132 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002133 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002134 }
2135
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002136 mDrawables.mOverride = false;
2137
Alan Viveretteb97d6982015-01-07 16:16:20 -08002138 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2139 dr.mShowing[Drawables.LEFT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002140 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002141 dr.mShowing[Drawables.LEFT] = left;
Romain Guy8e618e52010-03-08 12:18:20 -08002142
Alan Viveretteb97d6982015-01-07 16:16:20 -08002143 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2144 dr.mShowing[Drawables.TOP].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002145 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002146 dr.mShowing[Drawables.TOP] = top;
Romain Guy8e618e52010-03-08 12:18:20 -08002147
Alan Viveretteb97d6982015-01-07 16:16:20 -08002148 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2149 dr.mShowing[Drawables.RIGHT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002150 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002151 dr.mShowing[Drawables.RIGHT] = right;
Romain Guy8e618e52010-03-08 12:18:20 -08002152
Alan Viveretteb97d6982015-01-07 16:16:20 -08002153 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2154 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002155 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002156 dr.mShowing[Drawables.BOTTOM] = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002157
2158 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07002159 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002160
2161 state = getDrawableState();
2162
2163 if (left != null) {
2164 left.setState(state);
2165 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002166 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002167 dr.mDrawableSizeLeft = compoundRect.width();
2168 dr.mDrawableHeightLeft = compoundRect.height();
2169 } else {
2170 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2171 }
2172
2173 if (right != null) {
2174 right.setState(state);
2175 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002176 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002177 dr.mDrawableSizeRight = compoundRect.width();
2178 dr.mDrawableHeightRight = compoundRect.height();
2179 } else {
2180 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2181 }
2182
2183 if (top != null) {
2184 top.setState(state);
2185 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002186 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002187 dr.mDrawableSizeTop = compoundRect.height();
2188 dr.mDrawableWidthTop = compoundRect.width();
2189 } else {
2190 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2191 }
2192
2193 if (bottom != null) {
2194 bottom.setState(state);
2195 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002196 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002197 dr.mDrawableSizeBottom = compoundRect.height();
2198 dr.mDrawableWidthBottom = compoundRect.width();
2199 } else {
2200 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2201 }
2202 }
2203
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002204 // Save initial left/right drawables
2205 if (dr != null) {
2206 dr.mDrawableLeftInitial = left;
2207 dr.mDrawableRightInitial = right;
2208 }
2209
Fabrice Di Meglio3f5a90b2013-06-24 19:22:25 -07002210 resetResolvedDrawables();
2211 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08002212 applyCompoundDrawableTint();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002213 invalidate();
2214 requestLayout();
2215 }
2216
2217 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002218 * Sets the Drawables (if any) to appear to the left of, above, to the
2219 * right of, and below the text. Use 0 if you do not want a Drawable there.
2220 * The Drawables' bounds will be set to their intrinsic bounds.
2221 * <p>
2222 * Calling this method will overwrite any Drawables previously set using
2223 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002224 *
2225 * @param left Resource identifier of the left Drawable.
2226 * @param top Resource identifier of the top Drawable.
2227 * @param right Resource identifier of the right Drawable.
2228 * @param bottom Resource identifier of the bottom Drawable.
2229 *
2230 * @attr ref android.R.styleable#TextView_drawableLeft
2231 * @attr ref android.R.styleable#TextView_drawableTop
2232 * @attr ref android.R.styleable#TextView_drawableRight
2233 * @attr ref android.R.styleable#TextView_drawableBottom
2234 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002235 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002236 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2237 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002238 final Context context = getContext();
2239 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2240 top != 0 ? context.getDrawable(top) : null,
2241 right != 0 ? context.getDrawable(right) : null,
2242 bottom != 0 ? context.getDrawable(bottom) : null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002243 }
2244
2245 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002246 * Sets the Drawables (if any) to appear to the left of, above, to the
2247 * right of, and below the text. Use {@code null} if you do not want a
2248 * Drawable there. The Drawables' bounds will be set to their intrinsic
2249 * bounds.
2250 * <p>
2251 * Calling this method will overwrite any Drawables previously set using
2252 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002253 *
2254 * @attr ref android.R.styleable#TextView_drawableLeft
2255 * @attr ref android.R.styleable#TextView_drawableTop
2256 * @attr ref android.R.styleable#TextView_drawableRight
2257 * @attr ref android.R.styleable#TextView_drawableBottom
2258 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002259 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2260 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002261
2262 if (left != null) {
2263 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2264 }
2265 if (right != null) {
2266 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2267 }
2268 if (top != null) {
2269 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2270 }
2271 if (bottom != null) {
2272 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2273 }
2274 setCompoundDrawables(left, top, right, bottom);
2275 }
2276
2277 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002278 * Sets the Drawables (if any) to appear to the start of, above, to the end
2279 * of, and below the text. Use {@code null} if you do not want a Drawable
2280 * there. The Drawables must already have had {@link Drawable#setBounds}
2281 * called.
2282 * <p>
2283 * Calling this method will overwrite any Drawables previously set using
2284 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002285 *
2286 * @attr ref android.R.styleable#TextView_drawableStart
2287 * @attr ref android.R.styleable#TextView_drawableTop
2288 * @attr ref android.R.styleable#TextView_drawableEnd
2289 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002290 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002291 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2292 @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002293 Drawables dr = mDrawables;
2294
Alan Viverette97f84ee2014-09-09 16:55:56 -07002295 // We're switching to relative, discard absolute.
2296 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002297 if (dr.mShowing[Drawables.LEFT] != null) {
2298 dr.mShowing[Drawables.LEFT].setCallback(null);
2299 }
2300 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2301 if (dr.mShowing[Drawables.RIGHT] != null) {
2302 dr.mShowing[Drawables.RIGHT].setCallback(null);
2303 }
2304 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
Alan Viverette97f84ee2014-09-09 16:55:56 -07002305 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2306 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2307 }
2308
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002309 final boolean drawables = start != null || top != null
2310 || end != null || bottom != null;
2311
2312 if (!drawables) {
2313 // Clearing drawables... can we free the data structure?
2314 if (dr != null) {
2315 if (dr.mDrawablePadding == 0) {
2316 mDrawables = null;
2317 } else {
2318 // We need to retain the last set padding, so just clear
2319 // out all of the fields in the existing structure.
2320 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2321 dr.mDrawableStart = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002322 if (dr.mShowing[Drawables.TOP] != null) {
2323 dr.mShowing[Drawables.TOP].setCallback(null);
2324 }
2325 dr.mShowing[Drawables.TOP] = null;
2326 if (dr.mDrawableEnd != null) {
2327 dr.mDrawableEnd.setCallback(null);
2328 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002329 dr.mDrawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002330 if (dr.mShowing[Drawables.BOTTOM] != null) {
2331 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2332 }
2333 dr.mShowing[Drawables.BOTTOM] = null;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002334 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2335 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2336 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2337 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2338 }
2339 }
2340 } else {
2341 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002342 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002343 }
2344
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002345 mDrawables.mOverride = true;
2346
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002347 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2348 dr.mDrawableStart.setCallback(null);
2349 }
2350 dr.mDrawableStart = start;
2351
Alan Viveretteb97d6982015-01-07 16:16:20 -08002352 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2353 dr.mShowing[Drawables.TOP].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002354 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002355 dr.mShowing[Drawables.TOP] = top;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002356
2357 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2358 dr.mDrawableEnd.setCallback(null);
2359 }
2360 dr.mDrawableEnd = end;
2361
Alan Viveretteb97d6982015-01-07 16:16:20 -08002362 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2363 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002364 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002365 dr.mShowing[Drawables.BOTTOM] = bottom;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002366
2367 final Rect compoundRect = dr.mCompoundRect;
2368 int[] state;
2369
2370 state = getDrawableState();
2371
2372 if (start != null) {
2373 start.setState(state);
2374 start.copyBounds(compoundRect);
2375 start.setCallback(this);
2376 dr.mDrawableSizeStart = compoundRect.width();
2377 dr.mDrawableHeightStart = compoundRect.height();
2378 } else {
2379 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2380 }
2381
2382 if (end != null) {
2383 end.setState(state);
2384 end.copyBounds(compoundRect);
2385 end.setCallback(this);
2386 dr.mDrawableSizeEnd = compoundRect.width();
2387 dr.mDrawableHeightEnd = compoundRect.height();
2388 } else {
2389 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2390 }
2391
2392 if (top != null) {
2393 top.setState(state);
2394 top.copyBounds(compoundRect);
2395 top.setCallback(this);
2396 dr.mDrawableSizeTop = compoundRect.height();
2397 dr.mDrawableWidthTop = compoundRect.width();
2398 } else {
2399 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2400 }
2401
2402 if (bottom != null) {
2403 bottom.setState(state);
2404 bottom.copyBounds(compoundRect);
2405 bottom.setCallback(this);
2406 dr.mDrawableSizeBottom = compoundRect.height();
2407 dr.mDrawableWidthBottom = compoundRect.width();
2408 } else {
2409 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2410 }
2411 }
2412
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002413 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002414 resolveDrawables();
2415 invalidate();
2416 requestLayout();
2417 }
2418
2419 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002420 * Sets the Drawables (if any) to appear to the start of, above, to the end
2421 * of, and below the text. Use 0 if you do not want a Drawable there. The
2422 * Drawables' bounds will be set to their intrinsic bounds.
2423 * <p>
2424 * Calling this method will overwrite any Drawables previously set using
2425 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002426 *
2427 * @param start Resource identifier of the start Drawable.
2428 * @param top Resource identifier of the top Drawable.
2429 * @param end Resource identifier of the end Drawable.
2430 * @param bottom Resource identifier of the bottom Drawable.
2431 *
2432 * @attr ref android.R.styleable#TextView_drawableStart
2433 * @attr ref android.R.styleable#TextView_drawableTop
2434 * @attr ref android.R.styleable#TextView_drawableEnd
2435 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002436 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002437 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002438 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2439 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002440 final Context context = getContext();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002441 setCompoundDrawablesRelativeWithIntrinsicBounds(
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002442 start != 0 ? context.getDrawable(start) : null,
2443 top != 0 ? context.getDrawable(top) : null,
2444 end != 0 ? context.getDrawable(end) : null,
2445 bottom != 0 ? context.getDrawable(bottom) : null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002446 }
2447
2448 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002449 * Sets the Drawables (if any) to appear to the start of, above, to the end
2450 * of, and below the text. Use {@code null} if you do not want a Drawable
2451 * there. The Drawables' bounds will be set to their intrinsic bounds.
2452 * <p>
2453 * Calling this method will overwrite any Drawables previously set using
2454 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002455 *
2456 * @attr ref android.R.styleable#TextView_drawableStart
2457 * @attr ref android.R.styleable#TextView_drawableTop
2458 * @attr ref android.R.styleable#TextView_drawableEnd
2459 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002460 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002461 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2462 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002463
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002464 if (start != null) {
2465 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2466 }
2467 if (end != null) {
2468 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2469 }
2470 if (top != null) {
2471 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2472 }
2473 if (bottom != null) {
2474 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2475 }
2476 setCompoundDrawablesRelative(start, top, end, bottom);
2477 }
2478
2479 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002480 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002481 *
2482 * @attr ref android.R.styleable#TextView_drawableLeft
2483 * @attr ref android.R.styleable#TextView_drawableTop
2484 * @attr ref android.R.styleable#TextView_drawableRight
2485 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002486 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002487 @NonNull
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002488 public Drawable[] getCompoundDrawables() {
2489 final Drawables dr = mDrawables;
2490 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002491 return dr.mShowing.clone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002492 } else {
2493 return new Drawable[] { null, null, null, null };
2494 }
2495 }
2496
2497 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002498 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002499 *
2500 * @attr ref android.R.styleable#TextView_drawableStart
2501 * @attr ref android.R.styleable#TextView_drawableTop
2502 * @attr ref android.R.styleable#TextView_drawableEnd
2503 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002504 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002505 @NonNull
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002506 public Drawable[] getCompoundDrawablesRelative() {
2507 final Drawables dr = mDrawables;
2508 if (dr != null) {
2509 return new Drawable[] {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002510 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2511 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002512 };
2513 } else {
2514 return new Drawable[] { null, null, null, null };
2515 }
2516 }
2517
2518 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002519 * Sets the size of the padding between the compound drawables and
2520 * the text.
2521 *
2522 * @attr ref android.R.styleable#TextView_drawablePadding
2523 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002524 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002525 public void setCompoundDrawablePadding(int pad) {
2526 Drawables dr = mDrawables;
2527 if (pad == 0) {
2528 if (dr != null) {
2529 dr.mDrawablePadding = pad;
2530 }
2531 } else {
2532 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002533 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002534 }
2535 dr.mDrawablePadding = pad;
2536 }
2537
2538 invalidate();
2539 requestLayout();
2540 }
2541
2542 /**
2543 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002544 *
2545 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002546 */
2547 public int getCompoundDrawablePadding() {
2548 final Drawables dr = mDrawables;
2549 return dr != null ? dr.mDrawablePadding : 0;
2550 }
2551
Alan Viveretteb97d6982015-01-07 16:16:20 -08002552 /**
2553 * Applies a tint to the compound drawables. Does not modify the
2554 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2555 * <p>
2556 * Subsequent calls to
2557 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2558 * and related methods will automatically mutate the drawables and apply
2559 * the specified tint and tint mode using
2560 * {@link Drawable#setTintList(ColorStateList)}.
2561 *
2562 * @param tint the tint to apply, may be {@code null} to clear tint
2563 *
2564 * @attr ref android.R.styleable#TextView_drawableTint
2565 * @see #getCompoundDrawableTintList()
2566 * @see Drawable#setTintList(ColorStateList)
2567 */
2568 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2569 if (mDrawables == null) {
2570 mDrawables = new Drawables(getContext());
2571 }
2572 mDrawables.mTintList = tint;
2573 mDrawables.mHasTint = true;
2574
2575 applyCompoundDrawableTint();
2576 }
2577
2578 /**
2579 * @return the tint applied to the compound drawables
2580 * @attr ref android.R.styleable#TextView_drawableTint
2581 * @see #setCompoundDrawableTintList(ColorStateList)
2582 */
2583 public ColorStateList getCompoundDrawableTintList() {
2584 return mDrawables != null ? mDrawables.mTintList : null;
2585 }
2586
2587 /**
2588 * Specifies the blending mode used to apply the tint specified by
2589 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2590 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2591 *
2592 * @param tintMode the blending mode used to apply the tint, may be
2593 * {@code null} to clear tint
2594 * @attr ref android.R.styleable#TextView_drawableTintMode
2595 * @see #setCompoundDrawableTintList(ColorStateList)
2596 * @see Drawable#setTintMode(PorterDuff.Mode)
2597 */
2598 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2599 if (mDrawables == null) {
2600 mDrawables = new Drawables(getContext());
2601 }
2602 mDrawables.mTintMode = tintMode;
2603 mDrawables.mHasTintMode = true;
2604
2605 applyCompoundDrawableTint();
2606 }
2607
2608 /**
2609 * Returns the blending mode used to apply the tint to the compound
2610 * drawables, if specified.
2611 *
2612 * @return the blending mode used to apply the tint to the compound
2613 * drawables
2614 * @attr ref android.R.styleable#TextView_drawableTintMode
2615 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2616 */
2617 public PorterDuff.Mode getCompoundDrawableTintMode() {
2618 return mDrawables != null ? mDrawables.mTintMode : null;
2619 }
2620
2621 private void applyCompoundDrawableTint() {
2622 if (mDrawables == null) {
2623 return;
2624 }
2625
2626 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2627 final ColorStateList tintList = mDrawables.mTintList;
2628 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2629 final boolean hasTint = mDrawables.mHasTint;
2630 final boolean hasTintMode = mDrawables.mHasTintMode;
2631 final int[] state = getDrawableState();
2632
2633 for (Drawable dr : mDrawables.mShowing) {
2634 if (dr == null) {
2635 continue;
2636 }
2637
2638 if (dr == mDrawables.mDrawableError) {
2639 // From a developer's perspective, the error drawable isn't
2640 // a compound drawable. Don't apply the generic compound
2641 // drawable tint to it.
2642 continue;
2643 }
2644
2645 dr.mutate();
2646
2647 if (hasTint) {
2648 dr.setTintList(tintList);
2649 }
2650
2651 if (hasTintMode) {
2652 dr.setTintMode(tintMode);
2653 }
2654
2655 // The drawable (or one of its children) may not have been
2656 // stateful before applying the tint, so let's try again.
2657 if (dr.isStateful()) {
2658 dr.setState(state);
2659 }
2660 }
2661 }
2662 }
2663
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002664 @Override
2665 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002666 if (left != mPaddingLeft ||
2667 right != mPaddingRight ||
2668 top != mPaddingTop ||
2669 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002670 nullLayouts();
2671 }
2672
2673 // the super call will requestLayout()
2674 super.setPadding(left, top, right, bottom);
2675 invalidate();
2676 }
2677
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002678 @Override
2679 public void setPaddingRelative(int start, int top, int end, int bottom) {
2680 if (start != getPaddingStart() ||
2681 end != getPaddingEnd() ||
2682 top != mPaddingTop ||
2683 bottom != mPaddingBottom) {
2684 nullLayouts();
2685 }
2686
2687 // the super call will requestLayout()
2688 super.setPaddingRelative(start, top, end, bottom);
2689 invalidate();
2690 }
2691
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002692 /**
2693 * Gets the autolink mask of the text. See {@link
2694 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2695 * possible values.
2696 *
2697 * @attr ref android.R.styleable#TextView_autoLink
2698 */
2699 public final int getAutoLinkMask() {
2700 return mAutoLinkMask;
2701 }
2702
2703 /**
Alan Viverette38082272015-03-16 09:41:30 -07002704 * Sets the text appearance from the specified style resource.
2705 * <p>
2706 * Use a framework-defined {@code TextAppearance} style like
2707 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2708 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2709 * set of attributes that can be used in a custom style.
2710 *
2711 * @param resId the resource identifier of the style to apply
2712 * @attr ref android.R.styleable#TextView_textAppearance
2713 */
2714 @SuppressWarnings("deprecation")
2715 public void setTextAppearance(@StyleRes int resId) {
2716 setTextAppearance(mContext, resId);
2717 }
2718
2719 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002720 * Sets the text color, size, style, hint color, and highlight color
2721 * from the specified TextAppearance resource.
Alan Viverette38082272015-03-16 09:41:30 -07002722 *
2723 * @deprecated Use {@link #setTextAppearance(int)} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002724 */
Alan Viverette38082272015-03-16 09:41:30 -07002725 @Deprecated
2726 public void setTextAppearance(Context context, @StyleRes int resId) {
2727 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728
Alan Viverette38082272015-03-16 09:41:30 -07002729 final int textColorHighlight = ta.getColor(
2730 R.styleable.TextAppearance_textColorHighlight, 0);
2731 if (textColorHighlight != 0) {
2732 setHighlightColor(textColorHighlight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002733 }
2734
Alan Viverette38082272015-03-16 09:41:30 -07002735 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2736 if (textColor != null) {
2737 setTextColor(textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002738 }
2739
Alan Viverette38082272015-03-16 09:41:30 -07002740 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2741 if (textSize != 0) {
2742 setRawTextSize(textSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002743 }
2744
Alan Viverette38082272015-03-16 09:41:30 -07002745 final ColorStateList textColorHint = ta.getColorStateList(
2746 R.styleable.TextAppearance_textColorHint);
2747 if (textColorHint != null) {
2748 setHintTextColor(textColorHint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002749 }
2750
Alan Viverette38082272015-03-16 09:41:30 -07002751 final ColorStateList textColorLink = ta.getColorStateList(
2752 R.styleable.TextAppearance_textColorLink);
2753 if (textColorLink != null) {
2754 setLinkTextColor(textColorLink);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002755 }
2756
Alan Viverette38082272015-03-16 09:41:30 -07002757 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2758 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2759 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2760 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002761
Alan Viverette38082272015-03-16 09:41:30 -07002762 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2763 if (shadowColor != 0) {
2764 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2765 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2766 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2767 setShadowLayer(r, dx, dy, shadowColor);
Adam Powellac91df82013-02-14 13:48:47 -08002768 }
2769
Alan Viverette38082272015-03-16 09:41:30 -07002770 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
Adam Powell7f8f79a2011-07-07 18:35:54 -07002771 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2772 }
2773
Alan Viverette38082272015-03-16 09:41:30 -07002774 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2775 setElegantTextHeight(ta.getBoolean(
2776 R.styleable.TextAppearance_elegantTextHeight, false));
Raph Levien53c00772014-04-14 14:11:02 -07002777 }
2778
Alan Viverette38082272015-03-16 09:41:30 -07002779 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2780 setLetterSpacing(ta.getFloat(
2781 R.styleable.TextAppearance_letterSpacing, 0));
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002782 }
2783
Alan Viverette38082272015-03-16 09:41:30 -07002784 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2785 setFontFeatureSettings(ta.getString(
2786 R.styleable.TextAppearance_fontFeatureSettings));
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04002787 }
2788
Alan Viverette38082272015-03-16 09:41:30 -07002789 ta.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002790 }
2791
2792 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002793 * Get the default {@link Locale} of the text in this TextView.
2794 * @return the default {@link Locale} of the text in this TextView.
2795 */
2796 public Locale getTextLocale() {
2797 return mTextPaint.getTextLocale();
2798 }
2799
2800 /**
2801 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2802 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2803 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2804 *
2805 * @param locale the {@link Locale} for drawing text, must not be null.
2806 *
2807 * @see Paint#setTextLocale
2808 */
2809 public void setTextLocale(Locale locale) {
Raph Levien2e3aa442015-01-14 16:12:53 -08002810 mLocaleChanged = true;
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002811 mTextPaint.setTextLocale(locale);
2812 }
2813
Raph Levien2e3aa442015-01-14 16:12:53 -08002814 @Override
2815 protected void onConfigurationChanged(Configuration newConfig) {
2816 super.onConfigurationChanged(newConfig);
2817 if (!mLocaleChanged) {
2818 mTextPaint.setTextLocale(Locale.getDefault());
2819 }
2820 }
2821
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002822 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002823 * @return the size (in pixels) of the default text size in this TextView.
2824 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002825 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002826 public float getTextSize() {
2827 return mTextPaint.getTextSize();
2828 }
2829
2830 /**
Jon Mirandaa25dc4282014-07-01 14:35:07 -07002831 * @return the size (in scaled pixels) of thee default text size in this TextView.
2832 * @hide
2833 */
2834 @ViewDebug.ExportedProperty(category = "text")
2835 public float getScaledTextSize() {
2836 return mTextPaint.getTextSize() / mTextPaint.density;
2837 }
2838
2839 /** @hide */
2840 @ViewDebug.ExportedProperty(category = "text", mapping = {
2841 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2842 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2843 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2844 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2845 })
2846 public int getTypefaceStyle() {
2847 return mTextPaint.getTypeface().getStyle();
2848 }
2849
2850 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002851 * Set the default text size to the given value, interpreted as "scaled
2852 * pixel" units. This size is adjusted based on the current density and
2853 * user font size preference.
2854 *
2855 * @param size The scaled pixel size.
2856 *
2857 * @attr ref android.R.styleable#TextView_textSize
2858 */
2859 @android.view.RemotableViewMethod
2860 public void setTextSize(float size) {
2861 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2862 }
2863
2864 /**
2865 * Set the default text size to a given unit and value. See {@link
2866 * TypedValue} for the possible dimension units.
2867 *
2868 * @param unit The desired dimension unit.
2869 * @param size The desired size in the given units.
2870 *
2871 * @attr ref android.R.styleable#TextView_textSize
2872 */
2873 public void setTextSize(int unit, float size) {
2874 Context c = getContext();
2875 Resources r;
2876
2877 if (c == null)
2878 r = Resources.getSystem();
2879 else
2880 r = c.getResources();
2881
2882 setRawTextSize(TypedValue.applyDimension(
Alan Viverettecebc6ba2014-06-13 15:52:13 -07002883 unit, size, r.getDisplayMetrics()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002884 }
2885
2886 private void setRawTextSize(float size) {
2887 if (size != mTextPaint.getTextSize()) {
2888 mTextPaint.setTextSize(size);
2889
2890 if (mLayout != null) {
2891 nullLayouts();
2892 requestLayout();
2893 invalidate();
2894 }
2895 }
2896 }
2897
2898 /**
2899 * @return the extent by which text is currently being stretched
2900 * horizontally. This will usually be 1.
2901 */
2902 public float getTextScaleX() {
2903 return mTextPaint.getTextScaleX();
2904 }
2905
2906 /**
2907 * Sets the extent by which text should be stretched horizontally.
2908 *
2909 * @attr ref android.R.styleable#TextView_textScaleX
2910 */
2911 @android.view.RemotableViewMethod
2912 public void setTextScaleX(float size) {
2913 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002914 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002915 mTextPaint.setTextScaleX(size);
2916
2917 if (mLayout != null) {
2918 nullLayouts();
2919 requestLayout();
2920 invalidate();
2921 }
2922 }
2923 }
2924
2925 /**
2926 * Sets the typeface and style in which the text should be displayed.
2927 * Note that not all Typeface families actually have bold and italic
2928 * variants, so you may need to use
2929 * {@link #setTypeface(Typeface, int)} to get the appearance
2930 * that you actually want.
2931 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002932 * @see #getTypeface()
2933 *
Raph Leviend570e892012-05-09 11:45:34 -07002934 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002935 * @attr ref android.R.styleable#TextView_typeface
2936 * @attr ref android.R.styleable#TextView_textStyle
2937 */
2938 public void setTypeface(Typeface tf) {
2939 if (mTextPaint.getTypeface() != tf) {
2940 mTextPaint.setTypeface(tf);
2941
2942 if (mLayout != null) {
2943 nullLayouts();
2944 requestLayout();
2945 invalidate();
2946 }
2947 }
2948 }
2949
2950 /**
2951 * @return the current typeface and style in which the text is being
2952 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002953 *
2954 * @see #setTypeface(Typeface)
2955 *
Raph Leviend570e892012-05-09 11:45:34 -07002956 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002957 * @attr ref android.R.styleable#TextView_typeface
2958 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002959 */
2960 public Typeface getTypeface() {
2961 return mTextPaint.getTypeface();
2962 }
2963
2964 /**
Raph Levien53c00772014-04-14 14:11:02 -07002965 * Set the TextView's elegant height metrics flag. This setting selects font
2966 * variants that have not been compacted to fit Latin-based vertical
2967 * metrics, and also increases top and bottom bounds to provide more space.
2968 *
2969 * @param elegant set the paint's elegant metrics flag.
Raph Leviene1c4a0d2014-06-05 15:26:59 -07002970 *
2971 * @attr ref android.R.styleable#TextView_elegantTextHeight
Raph Levien53c00772014-04-14 14:11:02 -07002972 */
2973 public void setElegantTextHeight(boolean elegant) {
2974 mTextPaint.setElegantTextHeight(elegant);
2975 }
2976
2977 /**
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002978 * @return the extent by which text is currently being letter-spaced.
2979 * This will normally be 0.
2980 *
2981 * @see #setLetterSpacing(float)
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04002982 * @see Paint#setLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002983 */
2984 public float getLetterSpacing() {
2985 return mTextPaint.getLetterSpacing();
2986 }
2987
2988 /**
2989 * Sets text letter-spacing. The value is in 'EM' units. Typical values
2990 * for slight expansion will be around 0.05. Negative values tighten text.
2991 *
2992 * @see #getLetterSpacing()
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04002993 * @see Paint#getLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002994 *
2995 * @attr ref android.R.styleable#TextView_letterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002996 */
2997 @android.view.RemotableViewMethod
2998 public void setLetterSpacing(float letterSpacing) {
2999 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3000 mTextPaint.setLetterSpacing(letterSpacing);
3001
3002 if (mLayout != null) {
3003 nullLayouts();
3004 requestLayout();
3005 invalidate();
3006 }
3007 }
3008 }
3009
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003010 /**
3011 * @return the currently set font feature settings. Default is null.
3012 *
3013 * @see #setFontFeatureSettings(String)
3014 * @see Paint#setFontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003015 */
Raph Leviene272a262014-08-07 16:07:51 -07003016 @Nullable
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003017 public String getFontFeatureSettings() {
3018 return mTextPaint.getFontFeatureSettings();
3019 }
3020
3021 /**
Raph Levien39b4db72015-03-25 13:18:20 -07003022 * Sets the break strategy for breaking paragraphs into lines. The default value for
3023 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3024 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3025 * text "dancing" when being edited.
3026 *
3027 * @attr ref android.R.styleable#TextView_breakStrategy
3028 * @see #getBreakStrategy()
3029 */
3030 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3031 mBreakStrategy = breakStrategy;
3032 if (mLayout != null) {
3033 nullLayouts();
3034 requestLayout();
3035 invalidate();
3036 }
3037 }
3038
3039 /**
3040 * @return the currently set break strategy.
3041 *
3042 * @attr ref android.R.styleable#TextView_breakStrategy
3043 * @see #setBreakStrategy(int)
3044 */
3045 @Layout.BreakStrategy
3046 public int getBreakStrategy() {
3047 return mBreakStrategy;
3048 }
3049
3050 /**
Raph Leviene319d5a2015-04-14 23:51:07 -07003051 * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
3052 * pixels. For lines past the last element in the array, the last element repeats.
3053 *
3054 * @param leftIndents array of indent values for left margin, in pixels
3055 * @param rightIndents array of indent values for right margin, in pixels
3056 *
3057 * @see #getLeftIndents()
3058 * @see #getRightIndents()
3059 *
3060 * @attr ref android.R.styleable#TextView_leftIndents
3061 * @attr ref android.R.styleable#TextView_rightIndents
3062 */
3063 public void setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
3064 mLeftIndents = leftIndents;
3065 mRightIndents = rightIndents;
3066 if (mLayout != null) {
3067 nullLayouts();
3068 requestLayout();
3069 invalidate();
3070 }
3071 }
3072
3073 /**
3074 * Get left indents. See {#link setMargins} for more details.
3075 *
3076 * @return left indents
3077 * @see #setIndents(int[], int[])
3078 * @attr ref android.R.styleable#TextView_leftIndents
3079 */
3080 public int[] getLeftIndents() {
3081 return mLeftIndents;
3082 }
3083
3084 /**
3085 * Get right indents. See {#link setMargins} for more details.
3086 *
3087 * @return right indents
3088 * @see #setIndents(int[], int[])
3089 * @attr ref android.R.styleable#TextView_rightIndents
3090 */
3091 public int[] getRightIndents() {
3092 return mRightIndents;
3093 }
3094
3095 /**
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003096 * Sets font feature settings. The format is the same as the CSS
3097 * font-feature-settings attribute:
3098 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3099 *
Raph Leviene272a262014-08-07 16:07:51 -07003100 * @param fontFeatureSettings font feature settings represented as CSS compatible string
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003101 * @see #getFontFeatureSettings()
3102 * @see Paint#getFontFeatureSettings
3103 *
3104 * @attr ref android.R.styleable#TextView_fontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003105 */
3106 @android.view.RemotableViewMethod
Raph Leviene272a262014-08-07 16:07:51 -07003107 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003108 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3109 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3110
3111 if (mLayout != null) {
3112 nullLayouts();
3113 requestLayout();
3114 invalidate();
3115 }
3116 }
3117 }
3118
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003119
3120 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003121 * Sets the text color for all the states (normal, selected,
3122 * focused) to be this color.
3123 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003124 * @see #setTextColor(ColorStateList)
3125 * @see #getTextColors()
3126 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003127 * @attr ref android.R.styleable#TextView_textColor
3128 */
3129 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003130 public void setTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003131 mTextColor = ColorStateList.valueOf(color);
3132 updateTextColors();
3133 }
3134
3135 /**
3136 * Sets the text color.
3137 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003138 * @see #setTextColor(int)
3139 * @see #getTextColors()
3140 * @see #setHintTextColor(ColorStateList)
3141 * @see #setLinkTextColor(ColorStateList)
3142 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003143 * @attr ref android.R.styleable#TextView_textColor
3144 */
3145 public void setTextColor(ColorStateList colors) {
3146 if (colors == null) {
3147 throw new NullPointerException();
3148 }
3149
3150 mTextColor = colors;
3151 updateTextColors();
3152 }
3153
3154 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003155 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003156 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003157 * @see #setTextColor(ColorStateList)
3158 * @see #setTextColor(int)
3159 *
3160 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003161 */
3162 public final ColorStateList getTextColors() {
3163 return mTextColor;
3164 }
3165
3166 /**
3167 * <p>Return the current color selected for normal text.</p>
3168 *
3169 * @return Returns the current text color.
3170 */
Tor Norbye80756e32015-03-02 09:39:27 -08003171 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003172 public final int getCurrentTextColor() {
3173 return mCurTextColor;
3174 }
3175
3176 /**
3177 * Sets the color used to display the selection highlight.
3178 *
3179 * @attr ref android.R.styleable#TextView_textColorHighlight
3180 */
3181 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003182 public void setHighlightColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003183 if (mHighlightColor != color) {
3184 mHighlightColor = color;
3185 invalidate();
3186 }
3187 }
3188
3189 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003190 * @return the color used to display the selection highlight
3191 *
3192 * @see #setHighlightColor(int)
3193 *
3194 * @attr ref android.R.styleable#TextView_textColorHighlight
3195 */
Tor Norbye80756e32015-03-02 09:39:27 -08003196 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003197 public int getHighlightColor() {
3198 return mHighlightColor;
3199 }
3200
3201 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003202 * Sets whether the soft input method will be made visible when this
3203 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003204 */
3205 @android.view.RemotableViewMethod
3206 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003207 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003208 mEditor.mShowSoftInputOnFocus = show;
3209 }
3210
3211 /**
3212 * Returns whether the soft input method will be made visible when this
3213 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003214 */
3215 public final boolean getShowSoftInputOnFocus() {
3216 // When there is no Editor, return default true value
3217 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3218 }
3219
3220 /**
Chris Craik5faf85b2014-08-20 17:16:05 -07003221 * Gives the text a shadow of the specified blur radius and color, the specified
3222 * distance from its drawn position.
3223 * <p>
3224 * The text shadow produced does not interact with the properties on view
3225 * that are responsible for real time shadows,
3226 * {@link View#getElevation() elevation} and
3227 * {@link View#getTranslationZ() translationZ}.
3228 *
3229 * @see Paint#setShadowLayer(float, float, float, int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003230 *
3231 * @attr ref android.R.styleable#TextView_shadowColor
3232 * @attr ref android.R.styleable#TextView_shadowDx
3233 * @attr ref android.R.styleable#TextView_shadowDy
3234 * @attr ref android.R.styleable#TextView_shadowRadius
3235 */
3236 public void setShadowLayer(float radius, float dx, float dy, int color) {
3237 mTextPaint.setShadowLayer(radius, dx, dy, color);
3238
3239 mShadowRadius = radius;
3240 mShadowDx = dx;
3241 mShadowDy = dy;
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003242 mShadowColor = color;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003243
Gilles Debunne33b7de852012-03-12 11:57:48 -07003244 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07003245 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003246 invalidate();
3247 }
3248
3249 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003250 * Gets the radius of the shadow layer.
3251 *
3252 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3253 *
3254 * @see #setShadowLayer(float, float, float, int)
3255 *
3256 * @attr ref android.R.styleable#TextView_shadowRadius
3257 */
3258 public float getShadowRadius() {
3259 return mShadowRadius;
3260 }
3261
3262 /**
3263 * @return the horizontal offset of the shadow layer
3264 *
3265 * @see #setShadowLayer(float, float, float, int)
3266 *
3267 * @attr ref android.R.styleable#TextView_shadowDx
3268 */
3269 public float getShadowDx() {
3270 return mShadowDx;
3271 }
3272
3273 /**
3274 * @return the vertical offset of the shadow layer
3275 *
3276 * @see #setShadowLayer(float, float, float, int)
3277 *
3278 * @attr ref android.R.styleable#TextView_shadowDy
3279 */
3280 public float getShadowDy() {
3281 return mShadowDy;
3282 }
3283
3284 /**
3285 * @return the color of the shadow layer
3286 *
3287 * @see #setShadowLayer(float, float, float, int)
3288 *
3289 * @attr ref android.R.styleable#TextView_shadowColor
3290 */
Tor Norbye80756e32015-03-02 09:39:27 -08003291 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003292 public int getShadowColor() {
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003293 return mShadowColor;
Gilles Debunnef03acef2012-04-30 19:26:19 -07003294 }
3295
3296 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003297 * @return the base paint used for the text. Please use this only to
3298 * consult the Paint's properties and not to change them.
3299 */
3300 public TextPaint getPaint() {
3301 return mTextPaint;
3302 }
3303
3304 /**
3305 * Sets the autolink mask of the text. See {@link
3306 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3307 * possible values.
3308 *
3309 * @attr ref android.R.styleable#TextView_autoLink
3310 */
3311 @android.view.RemotableViewMethod
3312 public final void setAutoLinkMask(int mask) {
3313 mAutoLinkMask = mask;
3314 }
3315
3316 /**
3317 * Sets whether the movement method will automatically be set to
3318 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3319 * set to nonzero and links are detected in {@link #setText}.
3320 * The default is true.
3321 *
3322 * @attr ref android.R.styleable#TextView_linksClickable
3323 */
3324 @android.view.RemotableViewMethod
3325 public final void setLinksClickable(boolean whether) {
3326 mLinksClickable = whether;
3327 }
3328
3329 /**
3330 * Returns whether the movement method will automatically be set to
3331 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3332 * set to nonzero and links are detected in {@link #setText}.
3333 * The default is true.
3334 *
3335 * @attr ref android.R.styleable#TextView_linksClickable
3336 */
3337 public final boolean getLinksClickable() {
3338 return mLinksClickable;
3339 }
3340
3341 /**
3342 * Returns the list of URLSpans attached to the text
3343 * (by {@link Linkify} or otherwise) if any. You can call
3344 * {@link URLSpan#getURL} on them to find where they link to
3345 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3346 * to find the region of the text they are attached to.
3347 */
3348 public URLSpan[] getUrls() {
3349 if (mText instanceof Spanned) {
3350 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3351 } else {
3352 return new URLSpan[0];
3353 }
3354 }
3355
3356 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003357 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3358 * TextView.
3359 *
3360 * @see #setHintTextColor(ColorStateList)
3361 * @see #getHintTextColors()
3362 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003363 *
3364 * @attr ref android.R.styleable#TextView_textColorHint
3365 */
3366 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003367 public final void setHintTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003368 mHintTextColor = ColorStateList.valueOf(color);
3369 updateTextColors();
3370 }
3371
3372 /**
3373 * Sets the color of the hint text.
3374 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003375 * @see #getHintTextColors()
3376 * @see #setHintTextColor(int)
3377 * @see #setTextColor(ColorStateList)
3378 * @see #setLinkTextColor(ColorStateList)
3379 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003380 * @attr ref android.R.styleable#TextView_textColorHint
3381 */
3382 public final void setHintTextColor(ColorStateList colors) {
3383 mHintTextColor = colors;
3384 updateTextColors();
3385 }
3386
3387 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003388 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003389 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003390 * @see #setHintTextColor(ColorStateList)
3391 * @see #setHintTextColor(int)
3392 * @see #setTextColor(ColorStateList)
3393 * @see #setLinkTextColor(ColorStateList)
3394 *
3395 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003396 */
3397 public final ColorStateList getHintTextColors() {
3398 return mHintTextColor;
3399 }
3400
3401 /**
3402 * <p>Return the current color selected to paint the hint text.</p>
3403 *
3404 * @return Returns the current hint text color.
3405 */
Tor Norbye80756e32015-03-02 09:39:27 -08003406 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003407 public final int getCurrentHintTextColor() {
3408 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3409 }
3410
3411 /**
3412 * Sets the color of links in the text.
3413 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003414 * @see #setLinkTextColor(ColorStateList)
3415 * @see #getLinkTextColors()
3416 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003417 * @attr ref android.R.styleable#TextView_textColorLink
3418 */
3419 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003420 public final void setLinkTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003421 mLinkTextColor = ColorStateList.valueOf(color);
3422 updateTextColors();
3423 }
3424
3425 /**
3426 * Sets the color of links in the text.
3427 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003428 * @see #setLinkTextColor(int)
3429 * @see #getLinkTextColors()
3430 * @see #setTextColor(ColorStateList)
3431 * @see #setHintTextColor(ColorStateList)
3432 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003433 * @attr ref android.R.styleable#TextView_textColorLink
3434 */
3435 public final void setLinkTextColor(ColorStateList colors) {
3436 mLinkTextColor = colors;
3437 updateTextColors();
3438 }
3439
3440 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003441 * @return the list of colors used to paint the links in the text, for the different states of
3442 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003443 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003444 * @see #setLinkTextColor(ColorStateList)
3445 * @see #setLinkTextColor(int)
3446 *
3447 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003448 */
3449 public final ColorStateList getLinkTextColors() {
3450 return mLinkTextColor;
3451 }
3452
3453 /**
3454 * Sets the horizontal alignment of the text and the
3455 * vertical gravity that will be used when there is extra space
3456 * in the TextView beyond what is required for the text itself.
3457 *
3458 * @see android.view.Gravity
3459 * @attr ref android.R.styleable#TextView_gravity
3460 */
3461 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003462 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07003463 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003464 }
3465 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3466 gravity |= Gravity.TOP;
3467 }
3468
3469 boolean newLayout = false;
3470
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003471 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3472 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 newLayout = true;
3474 }
3475
3476 if (gravity != mGravity) {
3477 invalidate();
3478 }
3479
3480 mGravity = gravity;
3481
3482 if (mLayout != null && newLayout) {
3483 // XXX this is heavy-handed because no actual content changes.
3484 int want = mLayout.getWidth();
3485 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3486
3487 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3488 mRight - mLeft - getCompoundPaddingLeft() -
3489 getCompoundPaddingRight(), true);
3490 }
3491 }
3492
3493 /**
3494 * Returns the horizontal and vertical alignment of this TextView.
3495 *
3496 * @see android.view.Gravity
3497 * @attr ref android.R.styleable#TextView_gravity
3498 */
3499 public int getGravity() {
3500 return mGravity;
3501 }
3502
3503 /**
3504 * @return the flags on the Paint being used to display the text.
3505 * @see Paint#getFlags
3506 */
3507 public int getPaintFlags() {
3508 return mTextPaint.getFlags();
3509 }
3510
3511 /**
3512 * Sets flags on the Paint being used to display the text and
3513 * reflows the text if they are different from the old flags.
3514 * @see Paint#setFlags
3515 */
3516 @android.view.RemotableViewMethod
3517 public void setPaintFlags(int flags) {
3518 if (mTextPaint.getFlags() != flags) {
3519 mTextPaint.setFlags(flags);
3520
3521 if (mLayout != null) {
3522 nullLayouts();
3523 requestLayout();
3524 invalidate();
3525 }
3526 }
3527 }
3528
3529 /**
3530 * Sets whether the text should be allowed to be wider than the
3531 * View is. If false, it will be wrapped to the width of the View.
3532 *
3533 * @attr ref android.R.styleable#TextView_scrollHorizontally
3534 */
3535 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003536 if (mHorizontallyScrolling != whether) {
3537 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003538
Gilles Debunne22378292011-08-12 10:38:52 -07003539 if (mLayout != null) {
3540 nullLayouts();
3541 requestLayout();
3542 invalidate();
3543 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003544 }
3545 }
3546
3547 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003548 * Returns whether the text is allowed to be wider than the View is.
3549 * If false, the text will be wrapped to the width of the View.
3550 *
3551 * @attr ref android.R.styleable#TextView_scrollHorizontally
3552 * @hide
3553 */
3554 public boolean getHorizontallyScrolling() {
3555 return mHorizontallyScrolling;
3556 }
3557
3558 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003559 * Makes the TextView at least this many lines tall.
3560 *
3561 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3562 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003563 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003564 * @see #getMinLines()
3565 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003566 * @attr ref android.R.styleable#TextView_minLines
3567 */
3568 @android.view.RemotableViewMethod
3569 public void setMinLines(int minlines) {
3570 mMinimum = minlines;
3571 mMinMode = LINES;
3572
3573 requestLayout();
3574 invalidate();
3575 }
3576
3577 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003578 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3579 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3580 *
3581 * @see #setMinLines(int)
3582 *
3583 * @attr ref android.R.styleable#TextView_minLines
3584 */
3585 public int getMinLines() {
3586 return mMinMode == LINES ? mMinimum : -1;
3587 }
3588
3589 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003590 * Makes the TextView at least this many pixels tall.
3591 *
3592 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003593 *
3594 * @attr ref android.R.styleable#TextView_minHeight
3595 */
3596 @android.view.RemotableViewMethod
3597 public void setMinHeight(int minHeight) {
3598 mMinimum = minHeight;
3599 mMinMode = PIXELS;
3600
3601 requestLayout();
3602 invalidate();
3603 }
3604
3605 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003606 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3607 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3608 *
3609 * @see #setMinHeight(int)
3610 *
3611 * @attr ref android.R.styleable#TextView_minHeight
3612 */
3613 public int getMinHeight() {
3614 return mMinMode == PIXELS ? mMinimum : -1;
3615 }
3616
3617 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003618 * Makes the TextView at most this many lines tall.
3619 *
3620 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003621 *
3622 * @attr ref android.R.styleable#TextView_maxLines
3623 */
3624 @android.view.RemotableViewMethod
3625 public void setMaxLines(int maxlines) {
3626 mMaximum = maxlines;
3627 mMaxMode = LINES;
3628
3629 requestLayout();
3630 invalidate();
3631 }
3632
3633 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003634 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3635 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3636 *
3637 * @see #setMaxLines(int)
3638 *
3639 * @attr ref android.R.styleable#TextView_maxLines
3640 */
3641 public int getMaxLines() {
3642 return mMaxMode == LINES ? mMaximum : -1;
3643 }
3644
3645 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003646 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3647 * {@link #setMaxLines(int)} method.
3648 *
3649 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003650 *
3651 * @attr ref android.R.styleable#TextView_maxHeight
3652 */
3653 @android.view.RemotableViewMethod
3654 public void setMaxHeight(int maxHeight) {
3655 mMaximum = maxHeight;
3656 mMaxMode = PIXELS;
3657
3658 requestLayout();
3659 invalidate();
3660 }
3661
3662 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003663 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3664 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3665 *
3666 * @see #setMaxHeight(int)
3667 *
3668 * @attr ref android.R.styleable#TextView_maxHeight
3669 */
3670 public int getMaxHeight() {
3671 return mMaxMode == PIXELS ? mMaximum : -1;
3672 }
3673
3674 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003675 * Makes the TextView exactly this many lines tall.
3676 *
3677 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3678 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003679 *
3680 * @attr ref android.R.styleable#TextView_lines
3681 */
3682 @android.view.RemotableViewMethod
3683 public void setLines(int lines) {
3684 mMaximum = mMinimum = lines;
3685 mMaxMode = mMinMode = LINES;
3686
3687 requestLayout();
3688 invalidate();
3689 }
3690
3691 /**
3692 * Makes the TextView exactly this many pixels tall.
3693 * You could do the same thing by specifying this number in the
3694 * LayoutParams.
3695 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003696 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3697 * height setting.
3698 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003699 * @attr ref android.R.styleable#TextView_height
3700 */
3701 @android.view.RemotableViewMethod
3702 public void setHeight(int pixels) {
3703 mMaximum = mMinimum = pixels;
3704 mMaxMode = mMinMode = PIXELS;
3705
3706 requestLayout();
3707 invalidate();
3708 }
3709
3710 /**
3711 * Makes the TextView at least this many ems wide
3712 *
3713 * @attr ref android.R.styleable#TextView_minEms
3714 */
3715 @android.view.RemotableViewMethod
3716 public void setMinEms(int minems) {
3717 mMinWidth = minems;
3718 mMinWidthMode = EMS;
3719
3720 requestLayout();
3721 invalidate();
3722 }
3723
3724 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003725 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3726 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3727 *
3728 * @see #setMinEms(int)
3729 * @see #setEms(int)
3730 *
3731 * @attr ref android.R.styleable#TextView_minEms
3732 */
3733 public int getMinEms() {
3734 return mMinWidthMode == EMS ? mMinWidth : -1;
3735 }
3736
3737 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003738 * Makes the TextView at least this many pixels wide
3739 *
3740 * @attr ref android.R.styleable#TextView_minWidth
3741 */
3742 @android.view.RemotableViewMethod
3743 public void setMinWidth(int minpixels) {
3744 mMinWidth = minpixels;
3745 mMinWidthMode = PIXELS;
3746
3747 requestLayout();
3748 invalidate();
3749 }
3750
3751 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003752 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3753 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3754 *
3755 * @see #setMinWidth(int)
3756 * @see #setWidth(int)
3757 *
3758 * @attr ref android.R.styleable#TextView_minWidth
3759 */
3760 public int getMinWidth() {
3761 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3762 }
3763
3764 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003765 * Makes the TextView at most this many ems wide
3766 *
3767 * @attr ref android.R.styleable#TextView_maxEms
3768 */
3769 @android.view.RemotableViewMethod
3770 public void setMaxEms(int maxems) {
3771 mMaxWidth = maxems;
3772 mMaxWidthMode = EMS;
3773
3774 requestLayout();
3775 invalidate();
3776 }
3777
3778 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003779 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3780 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3781 *
3782 * @see #setMaxEms(int)
3783 * @see #setEms(int)
3784 *
3785 * @attr ref android.R.styleable#TextView_maxEms
3786 */
3787 public int getMaxEms() {
3788 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3789 }
3790
3791 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 * Makes the TextView at most this many pixels wide
3793 *
3794 * @attr ref android.R.styleable#TextView_maxWidth
3795 */
3796 @android.view.RemotableViewMethod
3797 public void setMaxWidth(int maxpixels) {
3798 mMaxWidth = maxpixels;
3799 mMaxWidthMode = PIXELS;
3800
3801 requestLayout();
3802 invalidate();
3803 }
3804
3805 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003806 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3807 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3808 *
3809 * @see #setMaxWidth(int)
3810 * @see #setWidth(int)
3811 *
3812 * @attr ref android.R.styleable#TextView_maxWidth
3813 */
3814 public int getMaxWidth() {
3815 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3816 }
3817
3818 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003819 * Makes the TextView exactly this many ems wide
3820 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003821 * @see #setMaxEms(int)
3822 * @see #setMinEms(int)
3823 * @see #getMinEms()
3824 * @see #getMaxEms()
3825 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003826 * @attr ref android.R.styleable#TextView_ems
3827 */
3828 @android.view.RemotableViewMethod
3829 public void setEms(int ems) {
3830 mMaxWidth = mMinWidth = ems;
3831 mMaxWidthMode = mMinWidthMode = EMS;
3832
3833 requestLayout();
3834 invalidate();
3835 }
3836
3837 /**
3838 * Makes the TextView exactly this many pixels wide.
3839 * You could do the same thing by specifying this number in the
3840 * LayoutParams.
3841 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003842 * @see #setMaxWidth(int)
3843 * @see #setMinWidth(int)
3844 * @see #getMinWidth()
3845 * @see #getMaxWidth()
3846 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003847 * @attr ref android.R.styleable#TextView_width
3848 */
3849 @android.view.RemotableViewMethod
3850 public void setWidth(int pixels) {
3851 mMaxWidth = mMinWidth = pixels;
3852 mMaxWidthMode = mMinWidthMode = PIXELS;
3853
3854 requestLayout();
3855 invalidate();
3856 }
3857
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003858 /**
3859 * Sets line spacing for this TextView. Each line will have its height
3860 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3861 *
3862 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3863 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3864 */
3865 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003866 if (mSpacingAdd != add || mSpacingMult != mult) {
3867 mSpacingAdd = add;
3868 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003869
Gilles Debunne22378292011-08-12 10:38:52 -07003870 if (mLayout != null) {
3871 nullLayouts();
3872 requestLayout();
3873 invalidate();
3874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003875 }
3876 }
3877
3878 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003879 * Gets the line spacing multiplier
3880 *
3881 * @return the value by which each line's height is multiplied to get its actual height.
3882 *
3883 * @see #setLineSpacing(float, float)
3884 * @see #getLineSpacingExtra()
3885 *
3886 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3887 */
3888 public float getLineSpacingMultiplier() {
3889 return mSpacingMult;
3890 }
3891
3892 /**
3893 * Gets the line spacing extra space
3894 *
3895 * @return the extra space that is added to the height of each lines of this TextView.
3896 *
3897 * @see #setLineSpacing(float, float)
3898 * @see #getLineSpacingMultiplier()
3899 *
3900 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3901 */
3902 public float getLineSpacingExtra() {
3903 return mSpacingAdd;
3904 }
3905
3906 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003907 * Convenience method: Append the specified text to the TextView's
3908 * display buffer, upgrading it to BufferType.EDITABLE if it was
3909 * not already editable.
3910 */
3911 public final void append(CharSequence text) {
3912 append(text, 0, text.length());
3913 }
3914
3915 /**
3916 * Convenience method: Append the specified text slice to the TextView's
3917 * display buffer, upgrading it to BufferType.EDITABLE if it was
3918 * not already editable.
3919 */
3920 public void append(CharSequence text, int start, int end) {
3921 if (!(mText instanceof Editable)) {
3922 setText(mText, BufferType.EDITABLE);
3923 }
3924
3925 ((Editable) mText).append(text, start, end);
3926 }
3927
3928 private void updateTextColors() {
3929 boolean inval = false;
3930 int color = mTextColor.getColorForState(getDrawableState(), 0);
3931 if (color != mCurTextColor) {
3932 mCurTextColor = color;
3933 inval = true;
3934 }
3935 if (mLinkTextColor != null) {
3936 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3937 if (color != mTextPaint.linkColor) {
3938 mTextPaint.linkColor = color;
3939 inval = true;
3940 }
3941 }
3942 if (mHintTextColor != null) {
3943 color = mHintTextColor.getColorForState(getDrawableState(), 0);
Raph Levienc1bf2852014-11-12 12:41:24 -08003944 if (color != mCurHintTextColor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003945 mCurHintTextColor = color;
Raph Levienc1bf2852014-11-12 12:41:24 -08003946 if (mText.length() == 0) {
3947 inval = true;
3948 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003949 }
3950 }
3951 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003952 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003953 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003954 invalidate();
3955 }
3956 }
3957
3958 @Override
3959 protected void drawableStateChanged() {
3960 super.drawableStateChanged();
3961 if (mTextColor != null && mTextColor.isStateful()
3962 || (mHintTextColor != null && mHintTextColor.isStateful())
3963 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3964 updateTextColors();
3965 }
3966
Alan Viveretteb97d6982015-01-07 16:16:20 -08003967 if (mDrawables != null) {
3968 final int[] state = getDrawableState();
3969 for (Drawable dr : mDrawables.mShowing) {
3970 if (dr != null && dr.isStateful()) {
3971 dr.setState(state);
3972 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003973 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003974 }
3975 }
3976
Alan Viverettecebc6ba2014-06-13 15:52:13 -07003977 @Override
Alan Viverette8de14942014-06-18 18:05:15 -07003978 public void drawableHotspotChanged(float x, float y) {
3979 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -07003980
Alan Viveretteb97d6982015-01-07 16:16:20 -08003981 if (mDrawables != null) {
3982 final int[] state = getDrawableState();
3983 for (Drawable dr : mDrawables.mShowing) {
3984 if (dr != null && dr.isStateful()) {
3985 dr.setHotspot(x, y);
3986 }
Alan Viverettecebc6ba2014-06-13 15:52:13 -07003987 }
3988 }
3989 }
3990
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003991 @Override
3992 public Parcelable onSaveInstanceState() {
3993 Parcelable superState = super.onSaveInstanceState();
3994
3995 // Save state if we are forced to
3996 boolean save = mFreezesText;
3997 int start = 0;
3998 int end = 0;
3999
4000 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004001 start = getSelectionStart();
4002 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004003 if (start >= 0 || end >= 0) {
4004 // Or save state if there is a selection
4005 save = true;
4006 }
4007 }
4008
4009 if (save) {
4010 SavedState ss = new SavedState(superState);
4011 // XXX Should also save the current scroll position!
4012 ss.selStart = start;
4013 ss.selEnd = end;
4014
4015 if (mText instanceof Spanned) {
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -07004016 Spannable sp = new SpannableStringBuilder(mText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004017
Gilles Debunne60e21862012-01-30 15:04:14 -08004018 if (mEditor != null) {
4019 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004020 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08004021 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07004022
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004023 ss.text = sp;
4024 } else {
4025 ss.text = mText.toString();
4026 }
4027
4028 if (isFocused() && start >= 0 && end >= 0) {
4029 ss.frozenWithFocus = true;
4030 }
4031
Gilles Debunne60e21862012-01-30 15:04:14 -08004032 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08004033
James Cookf59152c2015-02-26 18:03:58 -08004034 if (mEditor != null) {
4035 ss.editorState = mEditor.saveInstanceState();
4036 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004037 return ss;
4038 }
4039
4040 return superState;
4041 }
4042
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07004043 void removeMisspelledSpans(Spannable spannable) {
4044 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4045 SuggestionSpan.class);
4046 for (int i = 0; i < suggestionSpans.length; i++) {
4047 int flags = suggestionSpans[i].getFlags();
4048 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4049 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4050 spannable.removeSpan(suggestionSpans[i]);
4051 }
4052 }
4053 }
4054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004055 @Override
4056 public void onRestoreInstanceState(Parcelable state) {
4057 if (!(state instanceof SavedState)) {
4058 super.onRestoreInstanceState(state);
4059 return;
4060 }
4061
4062 SavedState ss = (SavedState)state;
4063 super.onRestoreInstanceState(ss.getSuperState());
4064
4065 // XXX restore buffer type too, as well as lots of other stuff
4066 if (ss.text != null) {
4067 setText(ss.text);
4068 }
4069
4070 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4071 if (mText instanceof Spannable) {
4072 int len = mText.length();
4073
4074 if (ss.selStart > len || ss.selEnd > len) {
4075 String restored = "";
4076
4077 if (ss.text != null) {
4078 restored = "(restored) ";
4079 }
4080
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004081 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004082 "/" + ss.selEnd + " out of range for " + restored +
4083 "text " + mText);
4084 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08004085 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004086
4087 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004088 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004089 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004090 }
4091 }
4092 }
4093 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08004094
4095 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07004096 final CharSequence error = ss.error;
4097 // Display the error later, after the first layout pass
4098 post(new Runnable() {
4099 public void run() {
Alexander Toresson545a8bb2013-10-14 14:59:31 +02004100 if (mEditor == null || !mEditor.mErrorWasChanged) {
4101 setError(error);
4102 }
Romain Guy9bc9fa12009-07-21 16:57:29 -07004103 }
4104 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08004105 }
James Cookf59152c2015-02-26 18:03:58 -08004106
4107 if (ss.editorState != null) {
4108 createEditorIfNeeded();
4109 mEditor.restoreInstanceState(ss.editorState);
4110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004111 }
4112
4113 /**
4114 * Control whether this text view saves its entire text contents when
4115 * freezing to an icicle, in addition to dynamic state such as cursor
4116 * position. By default this is false, not saving the text. Set to true
4117 * if the text in the text view is not being saved somewhere else in
4118 * persistent storage (such as in a content provider) so that if the
4119 * view is later thawed the user will not lose their data.
4120 *
4121 * @param freezesText Controls whether a frozen icicle should include the
4122 * entire text data: true to include it, false to not.
4123 *
4124 * @attr ref android.R.styleable#TextView_freezesText
4125 */
4126 @android.view.RemotableViewMethod
4127 public void setFreezesText(boolean freezesText) {
4128 mFreezesText = freezesText;
4129 }
4130
4131 /**
4132 * Return whether this text view is including its entire text contents
4133 * in frozen icicles.
4134 *
4135 * @return Returns true if text is included, false if it isn't.
4136 *
4137 * @see #setFreezesText
4138 */
4139 public boolean getFreezesText() {
4140 return mFreezesText;
4141 }
4142
4143 ///////////////////////////////////////////////////////////////////////////
4144
4145 /**
4146 * Sets the Factory used to create new Editables.
4147 */
4148 public final void setEditableFactory(Editable.Factory factory) {
4149 mEditableFactory = factory;
4150 setText(mText);
4151 }
4152
4153 /**
4154 * Sets the Factory used to create new Spannables.
4155 */
4156 public final void setSpannableFactory(Spannable.Factory factory) {
4157 mSpannableFactory = factory;
4158 setText(mText);
4159 }
4160
4161 /**
4162 * Sets the string value of the TextView. TextView <em>does not</em> accept
4163 * HTML-like formatting, which you can do with text strings in XML resource files.
4164 * To style your strings, attach android.text.style.* objects to a
4165 * {@link android.text.SpannableString SpannableString}, or see the
4166 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07004167 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004168 * formatted text in the XML resource file.
4169 *
4170 * @attr ref android.R.styleable#TextView_text
4171 */
4172 @android.view.RemotableViewMethod
4173 public final void setText(CharSequence text) {
4174 setText(text, mBufferType);
4175 }
4176
4177 /**
4178 * Like {@link #setText(CharSequence)},
4179 * except that the cursor position (if any) is retained in the new text.
4180 *
4181 * @param text The new text to place in the text view.
4182 *
4183 * @see #setText(CharSequence)
4184 */
4185 @android.view.RemotableViewMethod
4186 public final void setTextKeepState(CharSequence text) {
4187 setTextKeepState(text, mBufferType);
4188 }
4189
4190 /**
4191 * Sets the text that this TextView is to display (see
4192 * {@link #setText(CharSequence)}) and also sets whether it is stored
4193 * in a styleable/spannable buffer and whether it is editable.
4194 *
4195 * @attr ref android.R.styleable#TextView_text
4196 * @attr ref android.R.styleable#TextView_bufferType
4197 */
4198 public void setText(CharSequence text, BufferType type) {
4199 setText(text, type, true, 0);
4200
4201 if (mCharWrapper != null) {
4202 mCharWrapper.mChars = null;
4203 }
4204 }
4205
4206 private void setText(CharSequence text, BufferType type,
4207 boolean notifyBefore, int oldlen) {
4208 if (text == null) {
4209 text = "";
4210 }
4211
Luca Zanoline0760452011-09-08 12:03:37 +01004212 // If suggestions are not enabled, remove the suggestion spans from the text
4213 if (!isSuggestionsEnabled()) {
4214 text = removeSuggestionSpans(text);
4215 }
4216
Romain Guy939151f2009-04-08 14:22:40 -07004217 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4218
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004219 if (text instanceof Spanned &&
4220 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07004221 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4222 setHorizontalFadingEdgeEnabled(true);
4223 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4224 } else {
4225 setHorizontalFadingEdgeEnabled(false);
4226 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004228 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4229 }
4230
4231 int n = mFilters.length;
4232 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08004233 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004234 if (out != null) {
4235 text = out;
4236 }
4237 }
4238
4239 if (notifyBefore) {
4240 if (mText != null) {
4241 oldlen = mText.length();
4242 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4243 } else {
4244 sendBeforeTextChanged("", 0, 0, text.length());
4245 }
4246 }
4247
4248 boolean needEditableForNotification = false;
4249
4250 if (mListeners != null && mListeners.size() != 0) {
4251 needEditableForNotification = true;
4252 }
4253
Gilles Debunne2d373a12012-04-20 15:32:19 -07004254 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4255 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004256 createEditorIfNeeded();
James Cook48e0fac2015-02-25 15:44:51 -08004257 mEditor.forgetUndoRedo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004258 Editable t = mEditableFactory.newEditable(text);
4259 text = t;
4260 setFilters(t, mFilters);
4261 InputMethodManager imm = InputMethodManager.peekInstance();
4262 if (imm != null) imm.restartInput(this);
4263 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4264 text = mSpannableFactory.newSpannable(text);
4265 } else if (!(text instanceof CharWrapper)) {
4266 text = TextUtils.stringOrSpannedString(text);
4267 }
4268
4269 if (mAutoLinkMask != 0) {
4270 Spannable s2;
4271
4272 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4273 s2 = (Spannable) text;
4274 } else {
4275 s2 = mSpannableFactory.newSpannable(text);
4276 }
4277
4278 if (Linkify.addLinks(s2, mAutoLinkMask)) {
4279 text = s2;
4280 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4281
4282 /*
4283 * We must go ahead and set the text before changing the
4284 * movement method, because setMovementMethod() may call
4285 * setText() again to try to upgrade the buffer type.
4286 */
4287 mText = text;
4288
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004289 // Do not change the movement method for text that support text selection as it
4290 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07004291 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004292 setMovementMethod(LinkMovementMethod.getInstance());
4293 }
4294 }
4295 }
4296
4297 mBufferType = type;
4298 mText = text;
4299
Adam Powell7f8f79a2011-07-07 18:35:54 -07004300 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004301 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07004302 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004303 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07004304 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004305
4306 final int textLength = text.length();
4307
Adam Powell7f8f79a2011-07-07 18:35:54 -07004308 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004309 Spannable sp = (Spannable) text;
4310
Gilles Debunnec62589c2012-04-12 14:50:23 -07004311 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004312 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4313 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07004314 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004315 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07004316 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004317
Gilles Debunnec62589c2012-04-12 14:50:23 -07004318 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004319
4320 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08004321 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004322
Gilles Debunnec62589c2012-04-12 14:50:23 -07004323 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004324
4325 if (mTransformation != null) {
4326 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004327 }
4328
4329 if (mMovement != null) {
4330 mMovement.initialize(this, (Spannable) text);
4331
4332 /*
4333 * Initializing the movement method will have set the
4334 * selection, so reset mSelectionMoved to keep that from
4335 * interfering with the normal on-focus selection-setting.
4336 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07004337 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004338 }
4339 }
4340
4341 if (mLayout != null) {
4342 checkForRelayout();
4343 }
4344
4345 sendOnTextChanged(text, 0, oldlen, textLength);
4346 onTextChanged(text, 0, oldlen, textLength);
4347
Alan Viverette77e9a282013-09-12 17:16:09 -07004348 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4349
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004350 if (needEditableForNotification) {
4351 sendAfterTextChanged((Editable) text);
4352 }
Gilles Debunne05336272010-07-09 20:13:45 -07004353
Gilles Debunnebaaace52010-10-01 15:47:13 -07004354 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07004355 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004356 }
4357
4358 /**
4359 * Sets the TextView to display the specified slice of the specified
4360 * char array. You must promise that you will not change the contents
4361 * of the array except for right before another call to setText(),
4362 * since the TextView has no way to know that the text
4363 * has changed and that it needs to invalidate and re-layout.
4364 */
4365 public final void setText(char[] text, int start, int len) {
4366 int oldlen = 0;
4367
4368 if (start < 0 || len < 0 || start + len > text.length) {
4369 throw new IndexOutOfBoundsException(start + ", " + len);
4370 }
4371
4372 /*
4373 * We must do the before-notification here ourselves because if
4374 * the old text is a CharWrapper we destroy it before calling
4375 * into the normal path.
4376 */
4377 if (mText != null) {
4378 oldlen = mText.length();
4379 sendBeforeTextChanged(mText, 0, oldlen, len);
4380 } else {
4381 sendBeforeTextChanged("", 0, 0, len);
4382 }
4383
4384 if (mCharWrapper == null) {
4385 mCharWrapper = new CharWrapper(text, start, len);
4386 } else {
4387 mCharWrapper.set(text, start, len);
4388 }
4389
4390 setText(mCharWrapper, mBufferType, false, oldlen);
4391 }
4392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004393 /**
4394 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4395 * except that the cursor position (if any) is retained in the new text.
4396 *
4397 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4398 */
4399 public final void setTextKeepState(CharSequence text, BufferType type) {
4400 int start = getSelectionStart();
4401 int end = getSelectionEnd();
4402 int len = text.length();
4403
4404 setText(text, type);
4405
4406 if (start >= 0 || end >= 0) {
4407 if (mText instanceof Spannable) {
4408 Selection.setSelection((Spannable) mText,
4409 Math.max(0, Math.min(start, len)),
4410 Math.max(0, Math.min(end, len)));
4411 }
4412 }
4413 }
4414
4415 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004416 public final void setText(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004417 setText(getContext().getResources().getText(resid));
4418 }
4419
Tor Norbye7b9c9122013-05-30 16:48:33 -07004420 public final void setText(@StringRes int resid, BufferType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004421 setText(getContext().getResources().getText(resid), type);
4422 }
4423
4424 /**
4425 * Sets the text to be displayed when the text of the TextView is empty.
4426 * Null means to use the normal empty text. The hint does not currently
4427 * participate in determining the size of the view.
4428 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004429 * @attr ref android.R.styleable#TextView_hint
4430 */
4431 @android.view.RemotableViewMethod
4432 public final void setHint(CharSequence hint) {
4433 mHint = TextUtils.stringOrSpannedString(hint);
4434
4435 if (mLayout != null) {
4436 checkForRelayout();
4437 }
4438
Romain Guy4dc4f732009-06-19 15:16:40 -07004439 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004440 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07004441 }
Gilles Debunne626c3162012-02-14 15:46:41 -08004442
Gilles Debunne33b7de852012-03-12 11:57:48 -07004443 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08004444 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004445 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08004446 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004447 }
4448
4449 /**
4450 * Sets the text to be displayed when the text of the TextView is empty,
4451 * from a resource.
4452 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004453 * @attr ref android.R.styleable#TextView_hint
4454 */
4455 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004456 public final void setHint(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004457 setHint(getContext().getResources().getText(resid));
4458 }
4459
4460 /**
4461 * Returns the hint that is displayed when the text of the TextView
4462 * is empty.
4463 *
4464 * @attr ref android.R.styleable#TextView_hint
4465 */
4466 @ViewDebug.CapturedViewProperty
4467 public CharSequence getHint() {
4468 return mHint;
4469 }
4470
Gilles Debunned88876a2012-03-16 17:34:04 -07004471 boolean isSingleLine() {
4472 return mSingleLine;
4473 }
4474
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004475 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004476 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4477 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4478 }
4479
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004480 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07004481 * Removes the suggestion spans.
4482 */
4483 CharSequence removeSuggestionSpans(CharSequence text) {
4484 if (text instanceof Spanned) {
4485 Spannable spannable;
4486 if (text instanceof Spannable) {
4487 spannable = (Spannable) text;
4488 } else {
4489 spannable = new SpannableString(text);
4490 text = spannable;
4491 }
4492
4493 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4494 for (int i = 0; i < spans.length; i++) {
4495 spannable.removeSpan(spans[i]);
4496 }
4497 }
4498 return text;
4499 }
4500
4501 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004502 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4503 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4504 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4505 * then a soft keyboard will not be displayed for this text view.
4506 *
4507 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4508 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4509 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004510 *
4511 * @see #getInputType()
4512 * @see #setRawInputType(int)
4513 * @see android.text.InputType
4514 * @attr ref android.R.styleable#TextView_inputType
4515 */
4516 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004517 final boolean wasPassword = isPasswordInputType(getInputType());
4518 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004519 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004520 final boolean isPassword = isPasswordInputType(type);
4521 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004522 boolean forceUpdate = false;
4523 if (isPassword) {
4524 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07004525 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004526 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07004527 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4528 forceUpdate = true;
4529 }
Raph Leviend570e892012-05-09 11:45:34 -07004530 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004531 } else if (wasPassword || wasVisiblePassword) {
4532 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004533 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004534 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4535 forceUpdate = true;
4536 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004537 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004538
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004539 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004540
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004541 // We need to update the single line mode if it has changed or we
4542 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004543 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004544 // Change single line mode, but only change the transformation if
4545 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004546 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004547 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004548
Luca Zanoline0760452011-09-08 12:03:37 +01004549 if (!isSuggestionsEnabled()) {
4550 mText = removeSuggestionSpans(mText);
4551 }
4552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004553 InputMethodManager imm = InputMethodManager.peekInstance();
4554 if (imm != null) imm.restartInput(this);
4555 }
4556
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004557 /**
4558 * It would be better to rely on the input type for everything. A password inputType should have
4559 * a password transformation. We should hence use isPasswordInputType instead of this method.
4560 *
4561 * We should:
4562 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4563 * would install the correct transformation).
4564 * - Refuse the installation of a non-password transformation in setTransformation if the input
4565 * type is password.
4566 *
4567 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4568 * is useful since it matches what the user can see (obfuscated text or not).
4569 *
4570 * @return true if the current transformation method is of the password type.
4571 */
4572 private boolean hasPasswordTransformationMethod() {
4573 return mTransformation instanceof PasswordTransformationMethod;
4574 }
4575
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004576 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004577 final int variation =
4578 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004579 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004580 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4581 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004582 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4583 || variation
4584 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004585 }
4586
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004587 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004588 final int variation =
4589 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004590 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004591 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004592 }
4593
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004594 /**
4595 * Directly change the content type integer of the text view, without
4596 * modifying any other state.
4597 * @see #setInputType(int)
4598 * @see android.text.InputType
4599 * @attr ref android.R.styleable#TextView_inputType
4600 */
4601 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004602 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004603 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004604 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004605 }
4606
4607 private void setInputType(int type, boolean direct) {
4608 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4609 KeyListener input;
4610 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004611 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004612 TextKeyListener.Capitalize cap;
4613 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4614 cap = TextKeyListener.Capitalize.CHARACTERS;
4615 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4616 cap = TextKeyListener.Capitalize.WORDS;
4617 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4618 cap = TextKeyListener.Capitalize.SENTENCES;
4619 } else {
4620 cap = TextKeyListener.Capitalize.NONE;
4621 }
4622 input = TextKeyListener.getInstance(autotext, cap);
4623 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4624 input = DigitsKeyListener.getInstance(
4625 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4626 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4627 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4628 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4629 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4630 input = DateKeyListener.getInstance();
4631 break;
4632 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4633 input = TimeKeyListener.getInstance();
4634 break;
4635 default:
4636 input = DateTimeKeyListener.getInstance();
4637 break;
4638 }
4639 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4640 input = DialerKeyListener.getInstance();
4641 } else {
4642 input = TextKeyListener.getInstance();
4643 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004644 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004645 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004646 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004647 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004648 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004649 setKeyListenerOnly(input);
4650 }
4651 }
4652
4653 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004654 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004655 *
4656 * @see #setInputType(int)
4657 * @see android.text.InputType
4658 */
4659 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004660 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004661 }
4662
4663 /**
4664 * Change the editor type integer associated with the text view, which
4665 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4666 * has focus.
4667 * @see #getImeOptions
4668 * @see android.view.inputmethod.EditorInfo
4669 * @attr ref android.R.styleable#TextView_imeOptions
4670 */
4671 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004672 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004673 mEditor.createInputContentTypeIfNeeded();
4674 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004675 }
4676
4677 /**
4678 * Get the type of the IME editor.
4679 *
4680 * @see #setImeOptions(int)
4681 * @see android.view.inputmethod.EditorInfo
4682 */
4683 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004684 return mEditor != null && mEditor.mInputContentType != null
4685 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004686 }
4687
4688 /**
4689 * Change the custom IME action associated with the text view, which
4690 * will be reported to an IME with {@link EditorInfo#actionLabel}
4691 * and {@link EditorInfo#actionId} when it has focus.
4692 * @see #getImeActionLabel
4693 * @see #getImeActionId
4694 * @see android.view.inputmethod.EditorInfo
4695 * @attr ref android.R.styleable#TextView_imeActionLabel
4696 * @attr ref android.R.styleable#TextView_imeActionId
4697 */
4698 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004699 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004700 mEditor.createInputContentTypeIfNeeded();
4701 mEditor.mInputContentType.imeActionLabel = label;
4702 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004703 }
4704
4705 /**
4706 * Get the IME action label previous set with {@link #setImeActionLabel}.
4707 *
4708 * @see #setImeActionLabel
4709 * @see android.view.inputmethod.EditorInfo
4710 */
4711 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004712 return mEditor != null && mEditor.mInputContentType != null
4713 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004714 }
4715
4716 /**
4717 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4718 *
4719 * @see #setImeActionLabel
4720 * @see android.view.inputmethod.EditorInfo
4721 */
4722 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004723 return mEditor != null && mEditor.mInputContentType != null
4724 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004725 }
4726
4727 /**
4728 * Set a special listener to be called when an action is performed
4729 * on the text view. This will be called when the enter key is pressed,
4730 * or when an action supplied to the IME is selected by the user. Setting
4731 * this means that the normal hard key event will not insert a newline
4732 * into the text view, even if it is multi-line; holding down the ALT
4733 * modifier will, however, allow the user to insert a newline character.
4734 */
4735 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004736 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004737 mEditor.createInputContentTypeIfNeeded();
4738 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004739 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004740
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004741 /**
4742 * Called when an attached input method calls
4743 * {@link InputConnection#performEditorAction(int)
4744 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004745 * for this text view. The default implementation will call your action
4746 * listener supplied to {@link #setOnEditorActionListener}, or perform
4747 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004748 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4749 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004750 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004751 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004752 * <p>For backwards compatibility, if no IME options have been set and the
4753 * text view would not normally advance focus on enter, then
4754 * the NEXT and DONE actions received here will be turned into an enter
4755 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004756 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004757 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004758 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004759 * @see #setOnEditorActionListener
4760 */
4761 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004762 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004763 if (ict != null) {
4764 if (ict.onEditorActionListener != null) {
4765 if (ict.onEditorActionListener.onEditorAction(this,
4766 actionCode, null)) {
4767 return;
4768 }
4769 }
Gilles Debunne64794482011-11-30 15:45:28 -08004770
The Android Open Source Project4df24232009-03-05 14:34:35 -08004771 // This is the handling for some default action.
4772 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004773 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004774 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004775 // app may be expecting.
4776 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004777 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004778 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004779 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004780 throw new IllegalStateException("focus search returned a view " +
4781 "that wasn't able to take focus!");
4782 }
4783 }
4784 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004785
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004786 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004787 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004788 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004789 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004790 throw new IllegalStateException("focus search returned a view " +
4791 "that wasn't able to take focus!");
4792 }
4793 }
4794 return;
4795
The Android Open Source Project4df24232009-03-05 14:34:35 -08004796 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4797 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004798 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004799 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004800 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004801 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004802 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004803 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004804
Jeff Browna175a5b2012-02-15 19:18:31 -08004805 ViewRootImpl viewRootImpl = getViewRootImpl();
4806 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004807 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004808 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004809 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004810 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4811 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004812 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004813 | KeyEvent.FLAG_EDITOR_ACTION));
4814 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004815 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004816 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4817 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004818 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004819 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004820 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004821 }
Gilles Debunne64794482011-11-30 15:45:28 -08004822
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004823 /**
4824 * Set the private content type of the text, which is the
4825 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4826 * field that will be filled in when creating an input connection.
4827 *
4828 * @see #getPrivateImeOptions()
4829 * @see EditorInfo#privateImeOptions
4830 * @attr ref android.R.styleable#TextView_privateImeOptions
4831 */
4832 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004833 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004834 mEditor.createInputContentTypeIfNeeded();
4835 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004836 }
4837
4838 /**
4839 * Get the private type of the content.
4840 *
4841 * @see #setPrivateImeOptions(String)
4842 * @see EditorInfo#privateImeOptions
4843 */
4844 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004845 return mEditor != null && mEditor.mInputContentType != null
4846 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004847 }
4848
4849 /**
4850 * Set the extra input data of the text, which is the
4851 * {@link EditorInfo#extras TextBoxAttribute.extras}
4852 * Bundle that will be filled in when creating an input connection. The
4853 * given integer is the resource ID of an XML resource holding an
4854 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4855 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004856 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004857 * @see EditorInfo#extras
4858 * @attr ref android.R.styleable#TextView_editorExtras
4859 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07004860 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004861 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004862 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004863 mEditor.createInputContentTypeIfNeeded();
4864 mEditor.mInputContentType.extras = new Bundle();
4865 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004866 }
4867
4868 /**
4869 * Retrieve the input extras currently associated with the text view, which
4870 * can be viewed as well as modified.
4871 *
4872 * @param create If true, the extras will be created if they don't already
4873 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004874 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004875 * @see EditorInfo#extras
4876 * @attr ref android.R.styleable#TextView_editorExtras
4877 */
4878 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004879 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004880 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004881 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004882 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004883 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004884 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004885 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004886 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004887 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004888 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004889 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004890 }
4891
4892 /**
4893 * Returns the error message that was set to be displayed with
4894 * {@link #setError}, or <code>null</code> if no error was set
4895 * or if it the error was cleared by the widget after user input.
4896 */
4897 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004898 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004899 }
4900
4901 /**
4902 * Sets the right-hand compound drawable of the TextView to the "error"
4903 * icon and sets an error message that will be displayed in a popup when
4904 * the TextView has focus. The icon and error message will be reset to
4905 * null when any key events cause changes to the TextView's text. If the
4906 * <code>error</code> is <code>null</code>, the error message and icon
4907 * will be cleared.
4908 */
4909 @android.view.RemotableViewMethod
4910 public void setError(CharSequence error) {
4911 if (error == null) {
4912 setError(null, null);
4913 } else {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08004914 Drawable dr = getContext().getDrawable(
4915 com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004916
4917 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4918 setError(error, dr);
4919 }
4920 }
4921
4922 /**
4923 * Sets the right-hand compound drawable of the TextView to the specified
4924 * icon and sets an error message that will be displayed in a popup when
4925 * the TextView has focus. The icon and error message will be reset to
4926 * null when any key events cause changes to the TextView's text. The
4927 * drawable must already have had {@link Drawable#setBounds} set on it.
4928 * If the <code>error</code> is <code>null</code>, the error message will
4929 * be cleared (and you should provide a <code>null</code> icon as well).
4930 */
4931 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004932 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004933 mEditor.setError(error, icon);
Alan Viverette77e9a282013-09-12 17:16:09 -07004934 notifyViewAccessibilityStateChangedIfNeeded(
4935 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004936 }
4937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004938 @Override
4939 protected boolean setFrame(int l, int t, int r, int b) {
4940 boolean result = super.setFrame(l, t, r, b);
4941
Gilles Debunne2d373a12012-04-20 15:32:19 -07004942 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004943
Romain Guy986003d2009-03-25 17:42:35 -07004944 restartMarqueeIfNeeded();
4945
4946 return result;
4947 }
4948
4949 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004950 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4951 mRestartMarquee = false;
4952 startMarquee();
4953 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004954 }
4955
4956 /**
4957 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004958 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004959 *
4960 * @attr ref android.R.styleable#TextView_maxLength
4961 */
4962 public void setFilters(InputFilter[] filters) {
4963 if (filters == null) {
4964 throw new IllegalArgumentException();
4965 }
4966
4967 mFilters = filters;
4968
4969 if (mText instanceof Editable) {
4970 setFilters((Editable) mText, filters);
4971 }
4972 }
4973
4974 /**
4975 * Sets the list of input filters on the specified Editable,
4976 * and includes mInput in the list if it is an InputFilter.
4977 */
4978 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004979 if (mEditor != null) {
4980 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4981 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4982 int num = 0;
4983 if (undoFilter) num++;
4984 if (keyFilter) num++;
4985 if (num > 0) {
4986 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004987
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004988 System.arraycopy(filters, 0, nf, 0, filters.length);
4989 num = 0;
4990 if (undoFilter) {
4991 nf[filters.length] = mEditor.mUndoInputFilter;
4992 num++;
4993 }
4994 if (keyFilter) {
4995 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4996 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004997
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004998 e.setFilters(nf);
4999 return;
5000 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005001 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005002 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005003 }
5004
5005 /**
5006 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07005007 *
5008 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005009 */
5010 public InputFilter[] getFilters() {
5011 return mFilters;
5012 }
5013
5014 /////////////////////////////////////////////////////////////////////////
5015
Philip Milne7b757812012-09-19 18:13:44 -07005016 private int getBoxHeight(Layout l) {
5017 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5018 int padding = (l == mHintLayout) ?
5019 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5020 getExtendedPaddingTop() + getExtendedPaddingBottom();
5021 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5022 }
5023
Gilles Debunned88876a2012-03-16 17:34:04 -07005024 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005025 int voffset = 0;
5026 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5027
5028 Layout l = mLayout;
5029 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5030 l = mHintLayout;
5031 }
5032
5033 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07005034 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005035 int textht = l.getHeight();
5036
5037 if (textht < boxht) {
5038 if (gravity == Gravity.BOTTOM)
5039 voffset = boxht - textht;
5040 else // (gravity == Gravity.CENTER_VERTICAL)
5041 voffset = (boxht - textht) >> 1;
5042 }
5043 }
5044 return voffset;
5045 }
5046
5047 private int getBottomVerticalOffset(boolean forceNormal) {
5048 int voffset = 0;
5049 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5050
5051 Layout l = mLayout;
5052 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5053 l = mHintLayout;
5054 }
5055
5056 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07005057 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005058 int textht = l.getHeight();
5059
5060 if (textht < boxht) {
5061 if (gravity == Gravity.TOP)
5062 voffset = boxht - textht;
5063 else // (gravity == Gravity.CENTER_VERTICAL)
5064 voffset = (boxht - textht) >> 1;
5065 }
5066 }
5067 return voffset;
5068 }
5069
Gilles Debunned88876a2012-03-16 17:34:04 -07005070 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08005071 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005072 invalidateCursor();
5073 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005074 final int horizontalPadding = getCompoundPaddingLeft();
5075 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005076
Gilles Debunne2d373a12012-04-20 15:32:19 -07005077 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005078 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005079 /*
5080 * The reason for this concern about the thickness of the
5081 * cursor and doing the floor/ceil on the coordinates is that
5082 * some EditTexts (notably textfields in the Browser) have
5083 * anti-aliased text where not all the characters are
5084 * necessarily at integer-multiple locations. This should
5085 * make sure the entire cursor gets invalidated instead of
5086 * sometimes missing half a pixel.
5087 */
Neil Fuller33253a42014-10-01 11:55:10 +01005088 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005089 if (thick < 1.0f) {
5090 thick = 1.0f;
5091 }
5092
5093 thick /= 2.0f;
5094
Gilles Debunne83051b82012-02-24 20:01:13 -08005095 // mHighlightPath is guaranteed to be non null at that point.
5096 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005097
Neil Fuller33253a42014-10-01 11:55:10 +01005098 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5099 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5100 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5101 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005102 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005103 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005104 for (int i = 0; i < mEditor.mCursorCount; i++) {
5105 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005106 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5107 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005109 }
5110 }
5111 }
5112
Gilles Debunned88876a2012-03-16 17:34:04 -07005113 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07005114 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005115
5116 invalidateCursor(where, where, where);
5117 }
5118
5119 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005120 if (a >= 0 || b >= 0 || c >= 0) {
5121 int start = Math.min(Math.min(a, b), c);
5122 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08005123 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005124 }
5125 }
5126
5127 /**
5128 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08005129 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08005130 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005131 if (mLayout == null) {
5132 invalidate();
5133 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005134 int lineStart = mLayout.getLineForOffset(start);
5135 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005136
5137 // This is ridiculous, but the descent from the line above
5138 // can hang down into the line we really want to redraw,
5139 // so we have to invalidate part of the line above to make
5140 // sure everything that needs to be redrawn really is.
5141 // (But not the whole line above, because that would cause
5142 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08005143 if (lineStart > 0) {
5144 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005145 }
5146
Gilles Debunne8615ac92011-11-29 15:25:03 -08005147 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005148
Gilles Debunne8615ac92011-11-29 15:25:03 -08005149 if (start == end)
5150 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005151 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08005152 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005153
Gilles Debunne8615ac92011-11-29 15:25:03 -08005154 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005155
Gilles Debunne83051b82012-02-24 20:01:13 -08005156 // mEditor can be null in case selection is set programmatically.
5157 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005158 for (int i = 0; i < mEditor.mCursorCount; i++) {
5159 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08005160 top = Math.min(top, bounds.top);
5161 bottom = Math.max(bottom, bounds.bottom);
5162 }
5163 }
5164
Gilles Debunne8615ac92011-11-29 15:25:03 -08005165 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005166 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005167
5168 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08005169 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005170 left = (int) mLayout.getPrimaryHorizontal(start);
5171 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5172 left += compoundPaddingLeft;
5173 right += compoundPaddingLeft;
5174 } else {
5175 // Rectangle bounding box when the region spans several lines
5176 left = compoundPaddingLeft;
5177 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005178 }
5179
Gilles Debunne8615ac92011-11-29 15:25:03 -08005180 invalidate(mScrollX + left, verticalPadding + top,
5181 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005182 }
5183 }
5184
5185 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005186 if (!mPreDrawRegistered) {
5187 getViewTreeObserver().addOnPreDrawListener(this);
5188 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005189 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005190 }
5191
Craig Stoutf209ef92014-07-02 15:23:16 -07005192 private void unregisterForPreDraw() {
5193 getViewTreeObserver().removeOnPreDrawListener(this);
5194 mPreDrawRegistered = false;
5195 mPreDrawListenerDetached = false;
5196 }
5197
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005198 /**
5199 * {@inheritDoc}
5200 */
5201 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005202 if (mLayout == null) {
5203 assumeLayout();
5204 }
5205
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005206 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07005207 /* This code also provides auto-scrolling when a cursor is moved using a
5208 * CursorController (insertion point or selection limits).
5209 * For selection, ensure start or end is visible depending on controller's state.
5210 */
5211 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08005212 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005213 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5214 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005215 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07005216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005217
5218 /*
5219 * TODO: This should really only keep the end in view if
5220 * it already was before the text changed. I'm not sure
5221 * of a good way to tell from here if it was.
5222 */
Gilles Debunne60e21862012-01-30 15:04:14 -08005223 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005224 curs = mText.length();
5225 }
5226
5227 if (curs >= 0) {
Raph Leviene048f842013-09-27 13:36:24 -07005228 bringPointIntoView(curs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005229 }
5230 } else {
Raph Leviene048f842013-09-27 13:36:24 -07005231 bringTextIntoView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005232 }
5233
Gilles Debunne64e54a62010-09-07 19:07:17 -07005234 // This has to be checked here since:
5235 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5236 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005237 if (mEditor != null && mEditor.mCreatedWithASelection) {
Clara Bayarri29d2b5aa2015-03-13 17:41:56 +00005238 mEditor.startSelectionActionModeWithSelection();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005239 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08005240 }
5241
5242 // Phone specific code (there is no ExtractEditText on tablets).
5243 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
5244 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07005245 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Clara Bayarri29d2b5aa2015-03-13 17:41:56 +00005246 mEditor.startSelectionActionModeWithSelection();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07005247 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005248
Craig Stoutf209ef92014-07-02 15:23:16 -07005249 unregisterForPreDraw();
Gilles Debunne2e37d622012-01-27 13:54:00 -08005250
Raph Leviene048f842013-09-27 13:36:24 -07005251 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005252 }
5253
5254 @Override
5255 protected void onAttachedToWindow() {
5256 super.onAttachedToWindow();
5257
5258 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005259
Gilles Debunne2d373a12012-04-20 15:32:19 -07005260 if (mEditor != null) mEditor.onAttachedToWindow();
Craig Stoutf209ef92014-07-02 15:23:16 -07005261
5262 if (mPreDrawListenerDetached) {
5263 getViewTreeObserver().addOnPreDrawListener(this);
5264 mPreDrawListenerDetached = false;
5265 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005266 }
5267
John Reckb14dfe22014-03-06 22:06:20 +00005268 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005269 @Override
John Reckb14dfe22014-03-06 22:06:20 +00005270 protected void onDetachedFromWindowInternal() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005271 if (mPreDrawRegistered) {
5272 getViewTreeObserver().removeOnPreDrawListener(this);
Craig Stoutf209ef92014-07-02 15:23:16 -07005273 mPreDrawListenerDetached = true;
Gilles Debunne81f08082011-02-17 14:07:19 -08005274 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005275
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005276 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07005277
Gilles Debunne2d373a12012-04-20 15:32:19 -07005278 if (mEditor != null) mEditor.onDetachedFromWindow();
John Reckb14dfe22014-03-06 22:06:20 +00005279
5280 super.onDetachedFromWindowInternal();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005281 }
5282
5283 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08005284 public void onScreenStateChanged(int screenState) {
5285 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005286 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08005287 }
5288
5289 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005290 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005291 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005292 }
5293
5294 @Override
5295 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005296 return getCompoundPaddingLeft() - mPaddingLeft +
5297 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005298 }
5299
5300 @Override
5301 protected int getTopPaddingOffset() {
5302 return (int) Math.min(0, mShadowDy - mShadowRadius);
5303 }
5304
5305 @Override
5306 protected int getBottomPaddingOffset() {
5307 return (int) Math.max(0, mShadowDy + mShadowRadius);
5308 }
5309
Roozbeh Pournader7c0e7db2015-01-14 16:01:40 -08005310 private int getFudgedPaddingRight() {
5311 // Add sufficient space for cursor and tone marks
5312 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5313 return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5314 }
5315
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005316 @Override
5317 protected int getRightPaddingOffset() {
Roozbeh Pournader7c0e7db2015-01-14 16:01:40 -08005318 return -(getFudgedPaddingRight() - mPaddingRight) +
Romain Guy076dc9f2009-06-24 17:17:51 -07005319 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005320 }
5321
5322 @Override
5323 protected boolean verifyDrawable(Drawable who) {
5324 final boolean verified = super.verifyDrawable(who);
5325 if (!verified && mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005326 for (Drawable dr : mDrawables.mShowing) {
5327 if (who == dr) {
5328 return true;
5329 }
5330 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005331 }
5332 return verified;
5333 }
5334
5335 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07005336 public void jumpDrawablesToCurrentState() {
5337 super.jumpDrawablesToCurrentState();
5338 if (mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005339 for (Drawable dr : mDrawables.mShowing) {
5340 if (dr != null) {
5341 dr.jumpToCurrentState();
5342 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005343 }
Dianne Hackborne2136772010-11-04 15:08:59 -07005344 }
5345 }
5346
5347 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07005348 public void invalidateDrawable(Drawable drawable) {
Alan Viverettee6875f12014-02-05 14:05:17 -08005349 boolean handled = false;
5350
Romain Guy3c77d392009-05-20 11:26:50 -07005351 if (verifyDrawable(drawable)) {
5352 final Rect dirty = drawable.getBounds();
5353 int scrollX = mScrollX;
5354 int scrollY = mScrollY;
5355
5356 // IMPORTANT: The coordinates below are based on the coordinates computed
5357 // for each compound drawable in onDraw(). Make sure to update each section
5358 // accordingly.
5359 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07005360 if (drawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005361 if (drawable == drawables.mShowing[Drawables.LEFT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005362 final int compoundPaddingTop = getCompoundPaddingTop();
5363 final int compoundPaddingBottom = getCompoundPaddingBottom();
5364 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005365
Romain Guya6cd4e02009-05-20 15:09:21 -07005366 scrollX += mPaddingLeft;
5367 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005368 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005369 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005370 final int compoundPaddingTop = getCompoundPaddingTop();
5371 final int compoundPaddingBottom = getCompoundPaddingBottom();
5372 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005373
Romain Guya6cd4e02009-05-20 15:09:21 -07005374 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5375 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005376 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005377 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005378 final int compoundPaddingLeft = getCompoundPaddingLeft();
5379 final int compoundPaddingRight = getCompoundPaddingRight();
5380 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005381
Romain Guya6cd4e02009-05-20 15:09:21 -07005382 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5383 scrollY += mPaddingTop;
Alan Viverettee6875f12014-02-05 14:05:17 -08005384 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005385 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005386 final int compoundPaddingLeft = getCompoundPaddingLeft();
5387 final int compoundPaddingRight = getCompoundPaddingRight();
5388 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005389
Romain Guya6cd4e02009-05-20 15:09:21 -07005390 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5391 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
Alan Viverettee6875f12014-02-05 14:05:17 -08005392 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07005393 }
Romain Guy3c77d392009-05-20 11:26:50 -07005394 }
5395
Alan Viverettee6875f12014-02-05 14:05:17 -08005396 if (handled) {
5397 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5398 dirty.right + scrollX, dirty.bottom + scrollY);
5399 }
5400 }
5401
5402 if (!handled) {
5403 super.invalidateDrawable(drawable);
Romain Guy3c77d392009-05-20 11:26:50 -07005404 }
5405 }
5406
5407 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07005408 public boolean hasOverlappingRendering() {
Chris Craik7bcde502013-10-11 12:51:11 -07005409 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
Michael Jurka0931a852013-03-21 16:07:45 +01005410 return ((getBackground() != null && getBackground().getCurrent() != null)
Chris Craik7bcde502013-10-11 12:51:11 -07005411 || mText instanceof Spannable || hasSelection()
5412 || isHorizontalFadingEdgeEnabled());
Chet Haasedb8c9a62012-03-21 18:54:18 -07005413 }
5414
Gilles Debunne86b9c782010-11-11 10:43:48 -08005415 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08005416 *
Joe Malin10d96952013-05-29 17:49:09 -07005417 * Returns the state of the {@code textIsSelectable} flag (See
5418 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5419 * to allow users to select and copy text in a non-editable TextView, the content of an
5420 * {@link EditText} can always be selected, independently of the value of this flag.
5421 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08005422 *
5423 * @return True if the text displayed in this TextView can be selected by the user.
5424 *
5425 * @attr ref android.R.styleable#TextView_textIsSelectable
5426 */
5427 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005428 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005429 }
5430
5431 /**
Joe Malin10d96952013-05-29 17:49:09 -07005432 * Sets whether the content of this view is selectable by the user. The default is
5433 * {@code false}, meaning that the content is not selectable.
5434 * <p>
5435 * When you use a TextView to display a useful piece of information to the user (such as a
5436 * contact's address), make it selectable, so that the user can select and copy its
5437 * content. You can also use set the XML attribute
5438 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5439 * <p>
5440 * When you call this method to set the value of {@code textIsSelectable}, it sets
5441 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5442 * and {@code longClickable} to the same value. These flags correspond to the attributes
5443 * {@link android.R.styleable#View_focusable android:focusable},
5444 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5445 * {@link android.R.styleable#View_clickable android:clickable}, and
5446 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5447 * flags to a state you had set previously, call one or more of the following methods:
5448 * {@link #setFocusable(boolean) setFocusable()},
5449 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5450 * {@link #setClickable(boolean) setClickable()} or
5451 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08005452 *
Joe Malin10d96952013-05-29 17:49:09 -07005453 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08005454 */
5455 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005456 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08005457
Gilles Debunne5fae9962012-05-08 14:53:20 -07005458 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005459 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005460
Gilles Debunne2d373a12012-04-20 15:32:19 -07005461 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08005462 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005463 setFocusable(selectable);
5464 setClickable(selectable);
5465 setLongClickable(selectable);
5466
Gilles Debunne60e21862012-01-30 15:04:14 -08005467 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08005468
5469 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07005470 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005471
5472 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07005473 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08005474 }
5475
5476 @Override
5477 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08005478 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005479
Gilles Debunnefb817032011-01-13 13:52:49 -08005480 if (mSingleLine) {
5481 drawableState = super.onCreateDrawableState(extraSpace);
5482 } else {
5483 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005484 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5485 }
5486
Gilles Debunne60e21862012-01-30 15:04:14 -08005487 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08005488 // Disable pressed state, which was introduced when TextView was made clickable.
5489 // Prevents text color change.
5490 // setClickable(false) would have a similar effect, but it also disables focus changes
5491 // and long press actions, which are both needed by text selection.
5492 final int length = drawableState.length;
5493 for (int i = 0; i < length; i++) {
5494 if (drawableState[i] == R.attr.state_pressed) {
5495 final int[] nonPressedState = new int[length - 1];
5496 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5497 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5498 return nonPressedState;
5499 }
5500 }
5501 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005502
Gilles Debunne86b9c782010-11-11 10:43:48 -08005503 return drawableState;
5504 }
5505
Gilles Debunne83051b82012-02-24 20:01:13 -08005506 private Path getUpdatedHighlightPath() {
5507 Path highlight = null;
5508 Paint highlightPaint = mHighlightPaint;
5509
5510 final int selStart = getSelectionStart();
5511 final int selEnd = getSelectionEnd();
5512 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5513 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005514 if (mEditor != null && mEditor.isCursorVisible() &&
5515 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07005516 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08005517 if (mHighlightPathBogus) {
5518 if (mHighlightPath == null) mHighlightPath = new Path();
5519 mHighlightPath.reset();
5520 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005521 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08005522 mHighlightPathBogus = false;
5523 }
5524
5525 // XXX should pass to skin instead of drawing directly
5526 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005527 highlightPaint.setStyle(Paint.Style.STROKE);
5528 highlight = mHighlightPath;
5529 }
5530 } else {
5531 if (mHighlightPathBogus) {
5532 if (mHighlightPath == null) mHighlightPath = new Path();
5533 mHighlightPath.reset();
5534 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5535 mHighlightPathBogus = false;
5536 }
5537
5538 // XXX should pass to skin instead of drawing directly
5539 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005540 highlightPaint.setStyle(Paint.Style.FILL);
5541
5542 highlight = mHighlightPath;
5543 }
5544 }
5545 return highlight;
5546 }
5547
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005548 /**
5549 * @hide
5550 */
5551 public int getHorizontalOffsetForDrawables() {
5552 return 0;
5553 }
5554
Romain Guyc4d8eb62010-08-18 20:48:33 -07005555 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005556 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005557 restartMarqueeIfNeeded();
5558
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005559 // Draw the background for this view
5560 super.onDraw(canvas);
5561
5562 final int compoundPaddingLeft = getCompoundPaddingLeft();
5563 final int compoundPaddingTop = getCompoundPaddingTop();
5564 final int compoundPaddingRight = getCompoundPaddingRight();
5565 final int compoundPaddingBottom = getCompoundPaddingBottom();
5566 final int scrollX = mScrollX;
5567 final int scrollY = mScrollY;
5568 final int right = mRight;
5569 final int left = mLeft;
5570 final int bottom = mBottom;
5571 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005572 final boolean isLayoutRtl = isLayoutRtl();
5573 final int offset = getHorizontalOffsetForDrawables();
5574 final int leftOffset = isLayoutRtl ? 0 : offset;
5575 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005576
5577 final Drawables dr = mDrawables;
5578 if (dr != null) {
5579 /*
5580 * Compound, not extended, because the icon is not clipped
5581 * if the text height is smaller.
5582 */
5583
5584 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5585 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5586
Romain Guy3c77d392009-05-20 11:26:50 -07005587 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5588 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005589 if (dr.mShowing[Drawables.LEFT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005590 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005591 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005592 scrollY + compoundPaddingTop +
5593 (vspace - dr.mDrawableHeightLeft) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005594 dr.mShowing[Drawables.LEFT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005595 canvas.restore();
5596 }
5597
Romain Guy3c77d392009-05-20 11:26:50 -07005598 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5599 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005600 if (dr.mShowing[Drawables.RIGHT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005601 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005602 canvas.translate(scrollX + right - left - mPaddingRight
5603 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005604 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005605 dr.mShowing[Drawables.RIGHT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005606 canvas.restore();
5607 }
5608
Romain Guy3c77d392009-05-20 11:26:50 -07005609 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5610 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005611 if (dr.mShowing[Drawables.TOP] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005612 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005613 canvas.translate(scrollX + compoundPaddingLeft +
5614 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005615 dr.mShowing[Drawables.TOP].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005616 canvas.restore();
5617 }
5618
Romain Guy3c77d392009-05-20 11:26:50 -07005619 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5620 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005621 if (dr.mShowing[Drawables.BOTTOM] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005622 canvas.save();
5623 canvas.translate(scrollX + compoundPaddingLeft +
5624 (hspace - dr.mDrawableWidthBottom) / 2,
5625 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005626 dr.mShowing[Drawables.BOTTOM].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005627 canvas.restore();
5628 }
5629 }
5630
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005631 int color = mCurTextColor;
5632
5633 if (mLayout == null) {
5634 assumeLayout();
5635 }
5636
5637 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005638
5639 if (mHint != null && mText.length() == 0) {
5640 if (mHintTextColor != null) {
5641 color = mCurHintTextColor;
5642 }
5643
5644 layout = mHintLayout;
5645 }
5646
5647 mTextPaint.setColor(color);
5648 mTextPaint.drawableState = getDrawableState();
5649
5650 canvas.save();
5651 /* Would be faster if we didn't have to do this. Can we chop the
5652 (displayable) text so that we don't need to do this ever?
5653 */
5654
5655 int extendedPaddingTop = getExtendedPaddingTop();
5656 int extendedPaddingBottom = getExtendedPaddingBottom();
5657
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005658 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5659 final int maxScrollY = mLayout.getHeight() - vspace;
5660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005661 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005662 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
Roozbeh Pournader7c0e7db2015-01-14 16:01:40 -08005663 float clipRight = right - left - getFudgedPaddingRight() + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005664 float clipBottom = bottom - top + scrollY -
5665 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005666
5667 if (mShadowRadius != 0) {
5668 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5669 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5670
5671 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5672 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5673 }
5674
5675 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5676
5677 int voffsetText = 0;
5678 int voffsetCursor = 0;
5679
5680 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005681 /* shortcircuit calling getVerticaOffset() */
5682 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5683 voffsetText = getVerticalOffset(false);
5684 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005685 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005686 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005687
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005688 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005689 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005690 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5691 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005692 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005693 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005694 final int width = mRight - mLeft;
5695 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5696 final float dx = mLayout.getLineRight(0) - (width - padding);
Michael Lekman89bc4132012-01-11 14:27:52 +01005697 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005698 }
5699
5700 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005701 final float dx = -mMarquee.getScroll();
Michael Lekman89bc4132012-01-11 14:27:52 +01005702 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005703 }
5704 }
5705
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005706 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005707
Gilles Debunne83051b82012-02-24 20:01:13 -08005708 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005709 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005710 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005711 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005712 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005713 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005714
Gilles Debunned88876a2012-03-16 17:34:04 -07005715 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Michael Lekman89bc4132012-01-11 14:27:52 +01005716 final float dx = mMarquee.getGhostOffset();
5717 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005718 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005719 }
5720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005721 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005722 }
5723
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005724 @Override
5725 public void getFocusedRect(Rect r) {
5726 if (mLayout == null) {
5727 super.getFocusedRect(r);
5728 return;
5729 }
5730
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005731 int selEnd = getSelectionEnd();
5732 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005733 super.getFocusedRect(r);
5734 return;
5735 }
5736
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005737 int selStart = getSelectionStart();
5738 if (selStart < 0 || selStart >= selEnd) {
5739 int line = mLayout.getLineForOffset(selEnd);
5740 r.top = mLayout.getLineTop(line);
5741 r.bottom = mLayout.getLineBottom(line);
5742 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5743 r.right = r.left + 4;
5744 } else {
5745 int lineStart = mLayout.getLineForOffset(selStart);
5746 int lineEnd = mLayout.getLineForOffset(selEnd);
5747 r.top = mLayout.getLineTop(lineStart);
5748 r.bottom = mLayout.getLineBottom(lineEnd);
5749 if (lineStart == lineEnd) {
5750 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5751 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5752 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005753 // Selection extends across multiple lines -- make the focused
5754 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005755 if (mHighlightPathBogus) {
5756 if (mHighlightPath == null) mHighlightPath = new Path();
5757 mHighlightPath.reset();
5758 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5759 mHighlightPathBogus = false;
5760 }
5761 synchronized (TEMP_RECTF) {
5762 mHighlightPath.computeBounds(TEMP_RECTF, true);
5763 r.left = (int)TEMP_RECTF.left-1;
5764 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005765 }
5766 }
5767 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005768
5769 // Adjust for padding and gravity.
5770 int paddingLeft = getCompoundPaddingLeft();
5771 int paddingTop = getExtendedPaddingTop();
5772 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5773 paddingTop += getVerticalOffset(false);
5774 }
5775 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005776 int paddingBottom = getExtendedPaddingBottom();
5777 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005778 }
5779
5780 /**
5781 * Return the number of lines of text, or 0 if the internal Layout has not
5782 * been built.
5783 */
5784 public int getLineCount() {
5785 return mLayout != null ? mLayout.getLineCount() : 0;
5786 }
5787
5788 /**
5789 * Return the baseline for the specified line (0...getLineCount() - 1)
5790 * If bounds is not null, return the top, left, right, bottom extents
5791 * of the specified line in it. If the internal Layout has not been built,
5792 * return 0 and set bounds to (0, 0, 0, 0)
5793 * @param line which line to examine (0..getLineCount() - 1)
5794 * @param bounds Optional. If not null, it returns the extent of the line
5795 * @return the Y-coordinate of the baseline
5796 */
5797 public int getLineBounds(int line, Rect bounds) {
5798 if (mLayout == null) {
5799 if (bounds != null) {
5800 bounds.set(0, 0, 0, 0);
5801 }
5802 return 0;
5803 }
5804 else {
5805 int baseline = mLayout.getLineBounds(line, bounds);
5806
5807 int voffset = getExtendedPaddingTop();
5808 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5809 voffset += getVerticalOffset(true);
5810 }
5811 if (bounds != null) {
5812 bounds.offset(getCompoundPaddingLeft(), voffset);
5813 }
5814 return baseline + voffset;
5815 }
5816 }
5817
5818 @Override
5819 public int getBaseline() {
5820 if (mLayout == null) {
5821 return super.getBaseline();
5822 }
5823
5824 int voffset = 0;
5825 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5826 voffset = getVerticalOffset(true);
5827 }
5828
Philip Milne7b757812012-09-19 18:13:44 -07005829 if (isLayoutModeOptical(mParent)) {
5830 voffset -= getOpticalInsets().top;
5831 }
5832
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005833 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5834 }
5835
Romain Guyf2fc4602011-07-19 15:20:03 -07005836 /**
5837 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005838 */
5839 @Override
5840 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005841 if (mLayout == null) return 0;
5842
Romain Guyf2fc4602011-07-19 15:20:03 -07005843 int voffset = 0;
5844 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5845 voffset = getVerticalOffset(true);
5846 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005847
Romain Guyf2fc4602011-07-19 15:20:03 -07005848 if (offsetRequired) voffset += getTopPaddingOffset();
5849
5850 return getExtendedPaddingTop() + voffset;
5851 }
5852
5853 /**
5854 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005855 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005856 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005857 protected int getFadeHeight(boolean offsetRequired) {
5858 return mLayout != null ? mLayout.getHeight() : 0;
5859 }
5860
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005861 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005862 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5863 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005864 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005865
Gilles Debunne28294cc2011-08-24 12:02:05 -07005866 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005867 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5868 KeyEvent.DispatcherState state = getKeyDispatcherState();
5869 if (state != null) {
5870 state.startTracking(event, this);
5871 }
5872 return true;
5873 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5874 KeyEvent.DispatcherState state = getKeyDispatcherState();
5875 if (state != null) {
5876 state.handleUpEvent(event);
5877 }
5878 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005879 stopSelectionActionMode();
5880 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005881 }
5882 }
5883 }
5884 }
5885 return super.onKeyPreIme(keyCode, event);
5886 }
5887
5888 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005889 public boolean onKeyDown(int keyCode, KeyEvent event) {
5890 int which = doKeyDown(keyCode, event, null);
5891 if (which == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005892 return super.onKeyDown(keyCode, event);
5893 }
5894
5895 return true;
5896 }
5897
5898 @Override
5899 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005900 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005901
5902 int which = doKeyDown(keyCode, down, event);
5903 if (which == 0) {
5904 // Go through default dispatching.
5905 return super.onKeyMultiple(keyCode, repeatCount, event);
5906 }
5907 if (which == -1) {
5908 // Consumed the whole thing.
5909 return true;
5910 }
5911
5912 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005913
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005914 // We are going to dispatch the remaining events to either the input
5915 // or movement method. To do this, we will just send a repeated stream
5916 // of down and up events until we have done the complete repeatCount.
5917 // It would be nice if those interfaces had an onKeyMultiple() method,
5918 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005919 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005920 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005921 // mEditor and mEditor.mInput are not null from doKeyDown
5922 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005923 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005924 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5925 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005926 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005927 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005928
5929 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005930 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005931 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5932 while (--repeatCount > 0) {
5933 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5934 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5935 }
5936 }
5937
5938 return true;
5939 }
5940
5941 /**
5942 * Returns true if pressing ENTER in this field advances focus instead
5943 * of inserting the character. This is true mostly in single-line fields,
5944 * but also in mail addresses and subjects which will display on multiple
5945 * lines but where it doesn't make sense to insert newlines.
5946 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005947 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005948 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005949 return false;
5950 }
5951
5952 if (mSingleLine) {
5953 return true;
5954 }
5955
Gilles Debunne2d373a12012-04-20 15:32:19 -07005956 if (mEditor != null &&
5957 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5958 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005959 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5960 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005961 return true;
5962 }
5963 }
5964
5965 return false;
5966 }
5967
Jeff Brown4e6319b2010-12-13 10:36:51 -08005968 /**
5969 * Returns true if pressing TAB in this field advances focus instead
5970 * of inserting the character. Insert tabs only in multi-line editors.
5971 */
5972 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005973 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5974 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5975 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5976 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5977 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5978 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005979 }
5980 }
5981 return true;
5982 }
5983
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005984 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5985 if (!isEnabled()) {
5986 return 0;
5987 }
5988
Michael Wright3a7e4832013-02-11 15:55:50 -08005989 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5990 // While this shouldn't be necessary because any time we're preventing default movement we
5991 // should be restricting the focus to remain within this view, thus we'll also receive
5992 // the key up event, occasionally key up events will get dropped and we don't want to
5993 // prevent the user from traversing out of this on the next key down.
5994 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5995 mPreventDefaultMovement = false;
5996 }
5997
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005998 switch (keyCode) {
5999 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006000 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006001 // When mInputContentType is set, we know that we are
6002 // running in a "modern" cupcake environment, so don't need
6003 // to worry about the application trying to capture
6004 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006005 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006006 // If there is an action listener, given them a
6007 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006008 if (mEditor.mInputContentType.onEditorActionListener != null &&
6009 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07006010 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006011 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07006012 // We are consuming the enter key for them.
6013 return -1;
6014 }
6015 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006016
The Android Open Source Project10592532009-03-18 17:39:46 -07006017 // If our editor should move focus when enter is pressed, or
6018 // this is a generated event from an IME action button, then
6019 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08006020 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07006021 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07006022 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05006023 return 0;
6024 }
The Android Open Source Project10592532009-03-18 17:39:46 -07006025 return -1;
6026 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006027 }
The Android Open Source Project10592532009-03-18 17:39:46 -07006028 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006029
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006030 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006031 if (event.hasNoModifiers()) {
6032 if (shouldAdvanceFocusOnEnter()) {
6033 return 0;
6034 }
6035 }
6036 break;
6037
6038 case KeyEvent.KEYCODE_TAB:
6039 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6040 if (shouldAdvanceFocusOnTab()) {
6041 return 0;
6042 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006043 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006044 break;
6045
6046 // Has to be done on key down (and not on key up) to correctly be intercepted.
6047 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006048 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07006049 stopSelectionActionMode();
6050 return -1;
6051 }
6052 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006053 }
6054
Gilles Debunne2d373a12012-04-20 15:32:19 -07006055 if (mEditor != null && mEditor.mKeyListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006056 boolean doDown = true;
6057 if (otherEvent != null) {
6058 try {
6059 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006060 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6061 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006062 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006063 doDown = false;
6064 if (handled) {
6065 return -1;
6066 }
6067 } catch (AbstractMethodError e) {
6068 // onKeyOther was added after 1.0, so if it isn't
6069 // implemented we need to try to dispatch as a regular down.
6070 } finally {
6071 endBatchEdit();
6072 }
6073 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006074
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006075 if (doDown) {
6076 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006077 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6078 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006079 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08006080 hideErrorIfUnchanged();
6081 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006082 }
6083 }
6084
6085 // bug 650865: sometimes we get a key event before a layout.
6086 // don't try to move around if we don't know the layout.
6087
6088 if (mMovement != null && mLayout != null) {
6089 boolean doDown = true;
6090 if (otherEvent != null) {
6091 try {
6092 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6093 otherEvent);
6094 doDown = false;
6095 if (handled) {
6096 return -1;
6097 }
6098 } catch (AbstractMethodError e) {
6099 // onKeyOther was added after 1.0, so if it isn't
6100 // implemented we need to try to dispatch as a regular down.
6101 }
6102 }
6103 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08006104 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6105 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6106 mPreventDefaultMovement = true;
6107 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006108 return 2;
Michael Wright3a7e4832013-02-11 15:55:50 -08006109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006110 }
6111 }
6112
Michael Wright3a7e4832013-02-11 15:55:50 -08006113 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006114 }
6115
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006116 /**
6117 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6118 * can be recorded.
6119 * @hide
6120 */
6121 public void resetErrorChangedFlag() {
6122 /*
6123 * Keep track of what the error was before doing the input
6124 * so that if an input filter changed the error, we leave
6125 * that error showing. Otherwise, we take down whatever
6126 * error was showing when the user types something.
6127 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07006128 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006129 }
6130
6131 /**
6132 * @hide
6133 */
6134 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006135 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006136 setError(null, null);
6137 }
6138 }
6139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006140 @Override
6141 public boolean onKeyUp(int keyCode, KeyEvent event) {
6142 if (!isEnabled()) {
6143 return super.onKeyUp(keyCode, event);
6144 }
6145
Michael Wright3a7e4832013-02-11 15:55:50 -08006146 if (!KeyEvent.isModifierKey(keyCode)) {
6147 mPreventDefaultMovement = false;
6148 }
6149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006150 switch (keyCode) {
6151 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006152 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006153 /*
6154 * If there is a click listener, just call through to
6155 * super, which will invoke it.
6156 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08006157 * If there isn't a click listener, try to show the soft
6158 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006159 * call performClick(), but that won't do anything in
6160 * this case.)
6161 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006162 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006163 if (mMovement != null && mText instanceof Editable
6164 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08006165 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09006166 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07006167 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09006168 imm.showSoftInput(this, 0);
6169 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006170 }
6171 }
6172 }
6173 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006174
Jeff Brown4e6319b2010-12-13 10:36:51 -08006175 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006176 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006177 if (mEditor != null && mEditor.mInputContentType != null
6178 && mEditor.mInputContentType.onEditorActionListener != null
6179 && mEditor.mInputContentType.enterDown) {
6180 mEditor.mInputContentType.enterDown = false;
6181 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08006182 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006183 return true;
6184 }
6185 }
6186
Jeff Brown4e6319b2010-12-13 10:36:51 -08006187 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6188 || shouldAdvanceFocusOnEnter()) {
6189 /*
6190 * If there is a click listener, just call through to
6191 * super, which will invoke it.
6192 *
6193 * If there isn't a click listener, try to advance focus,
6194 * but still call through to super, which will reset the
6195 * pressed state and longpress state. (It will also
6196 * call performClick(), but that won't do anything in
6197 * this case.)
6198 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006199 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006200 View v = focusSearch(FOCUS_DOWN);
6201
6202 if (v != null) {
6203 if (!v.requestFocus(FOCUS_DOWN)) {
6204 throw new IllegalStateException(
6205 "focus search returned a view " +
6206 "that wasn't able to take focus!");
6207 }
6208
6209 /*
6210 * Return true because we handled the key; super
6211 * will return false because there was no click
6212 * listener.
6213 */
6214 super.onKeyUp(keyCode, event);
6215 return true;
6216 } else if ((event.getFlags()
6217 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6218 // No target for next focus, but make sure the IME
6219 // if this came from it.
6220 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08006221 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006222 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6223 }
6224 }
6225 }
6226 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006227 return super.onKeyUp(keyCode, event);
6228 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006229 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006230 }
6231
Gilles Debunne2d373a12012-04-20 15:32:19 -07006232 if (mEditor != null && mEditor.mKeyListener != null)
6233 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006234 return true;
6235
6236 if (mMovement != null && mLayout != null)
6237 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6238 return true;
6239
6240 return super.onKeyUp(keyCode, event);
6241 }
6242
Gilles Debunnec1714022012-01-17 13:59:23 -08006243 @Override
6244 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006245 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006246 }
Gilles Debunneb062e812011-09-27 14:58:37 -07006247
Gilles Debunnec1714022012-01-17 13:59:23 -08006248 @Override
6249 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03006250 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006251 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08006252 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006253 if (mEditor.mInputContentType != null) {
6254 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6255 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6256 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6257 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6258 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006259 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006260 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006261 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006262 if (focusSearch(FOCUS_DOWN) != null) {
6263 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6264 }
6265 if (focusSearch(FOCUS_UP) != null) {
6266 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6267 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006268 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6269 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006270 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006271 // An action has not been set, but the enter key will move to
6272 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006273 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006274 } else {
6275 // An action has not been set, and there is no focus to move
6276 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006277 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006278 }
6279 if (!shouldAdvanceFocusOnEnter()) {
6280 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006281 }
6282 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006283 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006284 // Multi-line text editors should always show an enter key.
6285 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6286 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006287 outAttrs.hintText = mHint;
6288 if (mText instanceof Editable) {
6289 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07006290 outAttrs.initialSelStart = getSelectionStart();
6291 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08006292 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006293 return ic;
6294 }
6295 }
6296 return null;
6297 }
6298
6299 /**
6300 * If this TextView contains editable content, extract a portion of it
6301 * based on the information in <var>request</var> in to <var>outText</var>.
6302 * @return Returns true if the text was successfully extracted, else false.
6303 */
Gilles Debunned88876a2012-03-16 17:34:04 -07006304 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07006305 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006306 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006307 }
Viktor Yakovel964be412010-02-17 08:35:57 +01006308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006309 /**
6310 * This is used to remove all style-impacting spans from text before new
6311 * extracted text is being replaced into it, so that we don't have any
6312 * lingering spans applied during the replace.
6313 */
6314 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6315 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6316 int i = spans.length;
6317 while (i > 0) {
6318 i--;
6319 spannable.removeSpan(spans[i]);
6320 }
6321 }
Gilles Debunned88876a2012-03-16 17:34:04 -07006322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006323 /**
6324 * Apply to this text view the given extracted text, as previously
6325 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6326 */
6327 public void setExtractedText(ExtractedText text) {
6328 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006329 if (text.text != null) {
6330 if (content == null) {
6331 setText(text.text, TextView.BufferType.EDITABLE);
6332 } else if (text.partialStartOffset < 0) {
6333 removeParcelableSpans(content, 0, content.length());
6334 content.replace(0, content.length(), text.text);
6335 } else {
6336 final int N = content.length();
6337 int start = text.partialStartOffset;
6338 if (start > N) start = N;
6339 int end = text.partialEndOffset;
6340 if (end > N) end = N;
6341 removeParcelableSpans(content, start, end);
6342 content.replace(start, end, text.text);
6343 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006344 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006345
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006346 // Now set the selection position... make sure it is in range, to
6347 // avoid crashes. If this is a partial update, it is possible that
6348 // the underlying text may have changed, causing us problems here.
6349 // Also we just don't want to trust clients to do the right thing.
6350 Spannable sp = (Spannable)getText();
6351 final int N = sp.length();
6352 int start = text.selectionStart;
6353 if (start < 0) start = 0;
6354 else if (start > N) start = N;
6355 int end = text.selectionEnd;
6356 if (end < 0) end = 0;
6357 else if (end > N) end = N;
6358 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006359
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006360 // Finally, update the selection mode.
6361 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6362 MetaKeyKeyListener.startSelecting(this, sp);
6363 } else {
6364 MetaKeyKeyListener.stopSelecting(this, sp);
6365 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006366 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006368 /**
6369 * @hide
6370 */
6371 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006372 if (mEditor.mInputMethodState != null) {
6373 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006374 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006375 // This would stop a possible selection mode, but no such mode is started in case
6376 // extracted mode will start. Some text is selected though, and will trigger an action mode
6377 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006378 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006379 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006381 /**
6382 * Called by the framework in response to a text completion from
6383 * the current input method, provided by it calling
6384 * {@link InputConnection#commitCompletion
6385 * InputConnection.commitCompletion()}. The default implementation does
6386 * nothing; text views that are supporting auto-completion should override
6387 * this to do their desired behavior.
6388 *
6389 * @param text The auto complete text the user has selected.
6390 */
6391 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006392 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006393 }
6394
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006395 /**
6396 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6397 * a dictionnary) from the current input method, provided by it calling
6398 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6399 * implementation flashes the background of the corrected word to provide feedback to the user.
6400 *
6401 * @param info The auto correct info about the text that was corrected.
6402 */
6403 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006404 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006405 }
6406
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006407 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006408 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006409 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006410
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006411 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006412 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006413 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006415 /**
6416 * Called by the framework in response to a request to begin a batch
6417 * of edit operations through a call to link {@link #beginBatchEdit()}.
6418 */
6419 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006420 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006421 }
Gilles Debunne60e21862012-01-30 15:04:14 -08006422
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006423 /**
6424 * Called by the framework in response to a request to end a batch
6425 * of edit operations through a call to link {@link #endBatchEdit}.
6426 */
6427 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006428 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006429 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006431 /**
6432 * Called by the framework in response to a private command from the
6433 * current method, provided by it calling
6434 * {@link InputConnection#performPrivateCommand
6435 * InputConnection.performPrivateCommand()}.
6436 *
6437 * @param action The action name of the command.
6438 * @param data Any additional data for the command. This may be null.
6439 * @return Return true if you handled the command, else false.
6440 */
6441 public boolean onPrivateIMECommand(String action, Bundle data) {
6442 return false;
6443 }
6444
6445 private void nullLayouts() {
6446 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6447 mSavedLayout = (BoringLayout) mLayout;
6448 }
6449 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6450 mSavedHintLayout = (BoringLayout) mHintLayout;
6451 }
6452
Adam Powell282e3772011-08-30 16:51:11 -07006453 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07006454
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08006455 mBoring = mHintBoring = null;
6456
Gilles Debunne77f18b02010-10-22 14:28:25 -07006457 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006458 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006459 }
6460
6461 /**
6462 * Make a new Layout based on the already-measured size of the view,
6463 * on the assumption that it was measured correctly at some point.
6464 */
6465 private void assumeLayout() {
6466 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6467
6468 if (width < 1) {
6469 width = 0;
6470 }
6471
6472 int physicalWidth = width;
6473
6474 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08006475 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006476 }
6477
6478 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6479 physicalWidth, false);
6480 }
6481
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006482 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006483 Layout.Alignment alignment;
6484 switch (getTextAlignment()) {
6485 case TEXT_ALIGNMENT_GRAVITY:
6486 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6487 case Gravity.START:
6488 alignment = Layout.Alignment.ALIGN_NORMAL;
6489 break;
6490 case Gravity.END:
6491 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6492 break;
6493 case Gravity.LEFT:
6494 alignment = Layout.Alignment.ALIGN_LEFT;
6495 break;
6496 case Gravity.RIGHT:
6497 alignment = Layout.Alignment.ALIGN_RIGHT;
6498 break;
6499 case Gravity.CENTER_HORIZONTAL:
6500 alignment = Layout.Alignment.ALIGN_CENTER;
6501 break;
6502 default:
6503 alignment = Layout.Alignment.ALIGN_NORMAL;
6504 break;
6505 }
6506 break;
6507 case TEXT_ALIGNMENT_TEXT_START:
6508 alignment = Layout.Alignment.ALIGN_NORMAL;
6509 break;
6510 case TEXT_ALIGNMENT_TEXT_END:
6511 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6512 break;
6513 case TEXT_ALIGNMENT_CENTER:
6514 alignment = Layout.Alignment.ALIGN_CENTER;
6515 break;
6516 case TEXT_ALIGNMENT_VIEW_START:
6517 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6518 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6519 break;
6520 case TEXT_ALIGNMENT_VIEW_END:
6521 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6522 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6523 break;
6524 case TEXT_ALIGNMENT_INHERIT:
6525 // This should never happen as we have already resolved the text alignment
6526 // but better safe than sorry so we just fall through
6527 default:
6528 alignment = Layout.Alignment.ALIGN_NORMAL;
6529 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006530 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006531 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006532 }
6533
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006534 /**
6535 * The width passed in is now the desired layout width,
6536 * not the full view width with padding.
6537 * {@hide}
6538 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07006539 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006540 BoringLayout.Metrics boring,
6541 BoringLayout.Metrics hintBoring,
6542 int ellipsisWidth, boolean bringIntoView) {
6543 stopMarquee();
6544
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006545 // Update "old" cached values
6546 mOldMaximum = mMaximum;
6547 mOldMaxMode = mMaxMode;
6548
Gilles Debunne83051b82012-02-24 20:01:13 -08006549 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006550
Gilles Debunne287d6c62011-10-05 18:22:11 -07006551 if (wantWidth < 0) {
6552 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006553 }
6554 if (hintWidth < 0) {
6555 hintWidth = 0;
6556 }
6557
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006558 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006559 final boolean testDirChange = mSingleLine && mLayout != null &&
6560 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6561 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6562 int oldDir = 0;
6563 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006564 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006565 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6566 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6567 TruncateAt effectiveEllipsize = mEllipsize;
6568 if (mEllipsize == TruncateAt.MARQUEE &&
6569 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006570 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006571 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006572
Doug Feltcb3791202011-07-07 11:57:48 -07006573 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006574 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006576
Gilles Debunne287d6c62011-10-05 18:22:11 -07006577 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006578 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6579 if (switchEllipsize) {
6580 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6581 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006582 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006583 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006584 }
6585
Romain Guy4dc4f732009-06-19 15:16:40 -07006586 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006587 mHintLayout = null;
6588
6589 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006590 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006591
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006592 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006593 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006594 mHintBoring);
6595 if (hintBoring != null) {
6596 mHintBoring = hintBoring;
6597 }
6598 }
6599
6600 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006601 if (hintBoring.width <= hintWidth &&
6602 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006603 if (mSavedHintLayout != null) {
6604 mHintLayout = mSavedHintLayout.
6605 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006606 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6607 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006608 } else {
6609 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006610 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6611 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006612 }
6613
6614 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006615 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6616 if (mSavedHintLayout != null) {
6617 mHintLayout = mSavedHintLayout.
6618 replaceOrMake(mHint, mTextPaint,
6619 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6620 hintBoring, mIncludePad, mEllipsize,
6621 ellipsisWidth);
6622 } else {
6623 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6624 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6625 hintBoring, mIncludePad, mEllipsize,
6626 ellipsisWidth);
6627 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006628 }
Raph Levien39b4db72015-03-25 13:18:20 -07006629 }
6630 // TODO: code duplication with makeSingleLayout()
6631 if (mHintLayout == null) {
6632 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6633 mHint.length(), hintWidth)
6634 .setPaint(mTextPaint)
6635 .setAlignment(alignment)
6636 .setTextDir(mTextDir)
6637 .setSpacingMult(mSpacingMult)
6638 .setSpacingAdd(mSpacingAdd)
6639 .setIncludePad(mIncludePad)
6640 .setBreakStrategy(mBreakStrategy);
Raph Leviene319d5a2015-04-14 23:51:07 -07006641 if (mLeftIndents != null || mRightIndents != null) {
6642 builder.setIndents(mLeftIndents, mRightIndents);
6643 }
Raph Levien39b4db72015-03-25 13:18:20 -07006644 if (shouldEllipsize) {
6645 builder.setEllipsize(mEllipsize)
6646 .setEllipsizedWidth(ellipsisWidth)
6647 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6648 }
6649 mHintLayout = builder.build();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006650 }
6651 }
6652
Raph Levienf5cf6c92013-04-12 11:31:31 -07006653 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006654 registerForPreDraw();
6655 }
6656
6657 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006658 if (!compressText(ellipsisWidth)) {
6659 final int height = mLayoutParams.height;
6660 // If the size of the view does not depend on the size of the text, try to
6661 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08006662 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006663 startMarquee();
6664 } else {
6665 // Defer the start of the marquee until we know our width (see setFrame())
6666 mRestartMarquee = true;
6667 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006668 }
6669 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006670
6671 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006672 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006673 }
6674
Gilles Debunne287d6c62011-10-05 18:22:11 -07006675 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006676 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6677 boolean useSaved) {
6678 Layout result = null;
6679 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006680 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Raph Levien39b4db72015-03-25 13:18:20 -07006681 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy,
6682 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006683 } else {
6684 if (boring == UNKNOWN_BORING) {
6685 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6686 if (boring != null) {
6687 mBoring = boring;
6688 }
6689 }
6690
6691 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006692 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006693 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6694 if (useSaved && mSavedLayout != null) {
6695 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006696 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006697 boring, mIncludePad);
6698 } else {
6699 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006700 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006701 boring, mIncludePad);
6702 }
6703
6704 if (useSaved) {
6705 mSavedLayout = (BoringLayout) result;
6706 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006707 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006708 if (useSaved && mSavedLayout != null) {
6709 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006710 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006711 boring, mIncludePad, effectiveEllipsize,
6712 ellipsisWidth);
6713 } else {
6714 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006715 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006716 boring, mIncludePad, effectiveEllipsize,
6717 ellipsisWidth);
6718 }
Adam Powell282e3772011-08-30 16:51:11 -07006719 }
Adam Powell282e3772011-08-30 16:51:11 -07006720 }
6721 }
Raph Levien39b4db72015-03-25 13:18:20 -07006722 if (result == null) {
6723 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6724 0, mTransformed.length(), wantWidth)
6725 .setPaint(mTextPaint)
6726 .setAlignment(alignment)
6727 .setTextDir(mTextDir)
6728 .setSpacingMult(mSpacingMult)
6729 .setSpacingAdd(mSpacingAdd)
6730 .setIncludePad(mIncludePad)
6731 .setBreakStrategy(mBreakStrategy);
Raph Leviene319d5a2015-04-14 23:51:07 -07006732 if (mLeftIndents != null || mRightIndents != null) {
6733 builder.setIndents(mLeftIndents, mRightIndents);
6734 }
Raph Levien39b4db72015-03-25 13:18:20 -07006735 if (shouldEllipsize) {
6736 builder.setEllipsize(effectiveEllipsize)
6737 .setEllipsizedWidth(ellipsisWidth)
6738 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6739 }
6740 // TODO: explore always setting maxLines
6741 result = builder.build();
6742 }
Adam Powell282e3772011-08-30 16:51:11 -07006743 return result;
6744 }
6745
Romain Guy939151f2009-04-08 14:22:40 -07006746 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006747 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006748
Romain Guy3373ed62009-05-04 14:13:32 -07006749 // Only compress the text if it hasn't been compressed by the previous pass
6750 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6751 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006752 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006753 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006754 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6755 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6756 post(new Runnable() {
6757 public void run() {
6758 requestLayout();
6759 }
6760 });
6761 return true;
6762 }
6763 }
6764
6765 return false;
6766 }
6767
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006768 private static int desired(Layout layout) {
6769 int n = layout.getLineCount();
6770 CharSequence text = layout.getText();
6771 float max = 0;
6772
6773 // if any line was wrapped, we can't use it.
6774 // but it's ok for the last line not to have a newline
6775
6776 for (int i = 0; i < n - 1; i++) {
6777 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6778 return -1;
6779 }
6780
6781 for (int i = 0; i < n; i++) {
6782 max = Math.max(max, layout.getLineWidth(i));
6783 }
6784
Neil Fuller33253a42014-10-01 11:55:10 +01006785 return (int) Math.ceil(max);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006786 }
6787
6788 /**
6789 * Set whether the TextView includes extra top and bottom padding to make
6790 * room for accents that go above the normal ascent and descent.
6791 * The default is true.
6792 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006793 * @see #getIncludeFontPadding()
6794 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006795 * @attr ref android.R.styleable#TextView_includeFontPadding
6796 */
6797 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006798 if (mIncludePad != includepad) {
6799 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006800
Gilles Debunne22378292011-08-12 10:38:52 -07006801 if (mLayout != null) {
6802 nullLayouts();
6803 requestLayout();
6804 invalidate();
6805 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006806 }
6807 }
6808
Gilles Debunnef03acef2012-04-30 19:26:19 -07006809 /**
6810 * Gets whether the TextView includes extra top and bottom padding to make
6811 * room for accents that go above the normal ascent and descent.
6812 *
6813 * @see #setIncludeFontPadding(boolean)
6814 *
6815 * @attr ref android.R.styleable#TextView_includeFontPadding
6816 */
6817 public boolean getIncludeFontPadding() {
6818 return mIncludePad;
6819 }
6820
Romain Guy4dc4f732009-06-19 15:16:40 -07006821 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006822
6823 @Override
6824 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6825 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6826 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6827 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6828 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6829
6830 int width;
6831 int height;
6832
6833 BoringLayout.Metrics boring = UNKNOWN_BORING;
6834 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6835
Doug Feltcb3791202011-07-07 11:57:48 -07006836 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07006837 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006838 }
6839
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006840 int des = -1;
6841 boolean fromexisting = false;
6842
6843 if (widthMode == MeasureSpec.EXACTLY) {
6844 // Parent has told us how big to be. So be it.
6845 width = widthSize;
6846 } else {
6847 if (mLayout != null && mEllipsize == null) {
6848 des = desired(mLayout);
6849 }
6850
6851 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006852 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006853 if (boring != null) {
6854 mBoring = boring;
6855 }
6856 } else {
6857 fromexisting = true;
6858 }
6859
6860 if (boring == null || boring == UNKNOWN_BORING) {
6861 if (des < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01006862 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006863 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006864 width = des;
6865 } else {
6866 width = boring.width;
6867 }
6868
6869 final Drawables dr = mDrawables;
6870 if (dr != null) {
6871 width = Math.max(width, dr.mDrawableWidthTop);
6872 width = Math.max(width, dr.mDrawableWidthBottom);
6873 }
6874
6875 if (mHint != null) {
6876 int hintDes = -1;
6877 int hintWidth;
6878
Romain Guy4dc4f732009-06-19 15:16:40 -07006879 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006880 hintDes = desired(mHintLayout);
6881 }
6882
6883 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006884 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006885 if (hintBoring != null) {
6886 mHintBoring = hintBoring;
6887 }
6888 }
6889
6890 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6891 if (hintDes < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01006892 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006893 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006894 hintWidth = hintDes;
6895 } else {
6896 hintWidth = hintBoring.width;
6897 }
6898
6899 if (hintWidth > width) {
6900 width = hintWidth;
6901 }
6902 }
6903
6904 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6905
6906 if (mMaxWidthMode == EMS) {
6907 width = Math.min(width, mMaxWidth * getLineHeight());
6908 } else {
6909 width = Math.min(width, mMaxWidth);
6910 }
6911
6912 if (mMinWidthMode == EMS) {
6913 width = Math.max(width, mMinWidth * getLineHeight());
6914 } else {
6915 width = Math.max(width, mMinWidth);
6916 }
6917
6918 // Check against our minimum width
6919 width = Math.max(width, getSuggestedMinimumWidth());
6920
6921 if (widthMode == MeasureSpec.AT_MOST) {
6922 width = Math.min(widthSize, width);
6923 }
6924 }
6925
6926 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6927 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006928
Jeff Brown033a0012011-11-11 15:30:16 -08006929 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006930
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006931 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006932 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006933
6934 if (mLayout == null) {
6935 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006936 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006937 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006938 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6939 (hintWidth != hintWant) ||
6940 (mLayout.getEllipsizedWidth() !=
6941 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6942
6943 final boolean widthChanged = (mHint == null) &&
6944 (mEllipsize == null) &&
6945 (want > mLayout.getWidth()) &&
6946 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6947
6948 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6949
6950 if (layoutChanged || maximumChanged) {
6951 if (!maximumChanged && widthChanged) {
6952 mLayout.increaseWidthTo(want);
6953 } else {
6954 makeNewLayout(want, hintWant, boring, hintBoring,
6955 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6956 }
6957 } else {
6958 // Nothing has changed
6959 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006960 }
6961
6962 if (heightMode == MeasureSpec.EXACTLY) {
6963 // Parent has told us how big to be. So be it.
6964 height = heightSize;
6965 mDesiredHeightAtMeasure = -1;
6966 } else {
6967 int desired = getDesiredHeight();
6968
6969 height = desired;
6970 mDesiredHeightAtMeasure = desired;
6971
6972 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006973 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006974 }
6975 }
6976
Romain Guy4dc4f732009-06-19 15:16:40 -07006977 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006978 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006979 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006980 }
6981
6982 /*
6983 * We didn't let makeNewLayout() register to bring the cursor into view,
6984 * so do it here if there is any possibility that it is needed.
6985 */
6986 if (mMovement != null ||
6987 mLayout.getWidth() > unpaddedWidth ||
6988 mLayout.getHeight() > unpaddedHeight) {
6989 registerForPreDraw();
6990 } else {
6991 scrollTo(0, 0);
6992 }
6993
6994 setMeasuredDimension(width, height);
6995 }
6996
6997 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006998 return Math.max(
6999 getDesiredHeight(mLayout, true),
7000 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007001 }
7002
7003 private int getDesiredHeight(Layout layout, boolean cap) {
7004 if (layout == null) {
7005 return 0;
7006 }
7007
7008 int linecount = layout.getLineCount();
7009 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7010 int desired = layout.getLineTop(linecount);
7011
7012 final Drawables dr = mDrawables;
7013 if (dr != null) {
7014 desired = Math.max(desired, dr.mDrawableHeightLeft);
7015 desired = Math.max(desired, dr.mDrawableHeightRight);
7016 }
7017
7018 desired += pad;
7019
7020 if (mMaxMode == LINES) {
7021 /*
7022 * Don't cap the hint to a certain number of lines.
7023 * (Do cap it, though, if we have a maximum pixel height.)
7024 */
7025 if (cap) {
7026 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08007027 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007028
7029 if (dr != null) {
7030 desired = Math.max(desired, dr.mDrawableHeightLeft);
7031 desired = Math.max(desired, dr.mDrawableHeightRight);
7032 }
7033
7034 desired += pad;
7035 linecount = mMaximum;
7036 }
7037 }
7038 } else {
7039 desired = Math.min(desired, mMaximum);
7040 }
7041
7042 if (mMinMode == LINES) {
7043 if (linecount < mMinimum) {
7044 desired += getLineHeight() * (mMinimum - linecount);
7045 }
7046 } else {
7047 desired = Math.max(desired, mMinimum);
7048 }
7049
7050 // Check against our minimum height
7051 desired = Math.max(desired, getSuggestedMinimumHeight());
7052
7053 return desired;
7054 }
7055
7056 /**
7057 * Check whether a change to the existing text layout requires a
7058 * new view layout.
7059 */
7060 private void checkForResize() {
7061 boolean sizeChanged = false;
7062
7063 if (mLayout != null) {
7064 // Check if our width changed
7065 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7066 sizeChanged = true;
7067 invalidate();
7068 }
7069
7070 // Check if our height changed
7071 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7072 int desiredHeight = getDesiredHeight();
7073
7074 if (desiredHeight != this.getHeight()) {
7075 sizeChanged = true;
7076 }
Romain Guy980a9382010-01-08 15:06:28 -08007077 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007078 if (mDesiredHeightAtMeasure >= 0) {
7079 int desiredHeight = getDesiredHeight();
7080
7081 if (desiredHeight != mDesiredHeightAtMeasure) {
7082 sizeChanged = true;
7083 }
7084 }
7085 }
7086 }
7087
7088 if (sizeChanged) {
7089 requestLayout();
7090 // caller will have already invalidated
7091 }
7092 }
7093
7094 /**
7095 * Check whether entirely new text requires a new view layout
7096 * or merely a new text layout.
7097 */
7098 private void checkForRelayout() {
7099 // If we have a fixed width, we can just swap in a new text layout
7100 // if the text height stays the same or if the view height is fixed.
7101
7102 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7103 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7104 (mHint == null || mHintLayout != null) &&
7105 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7106 // Static width, so try making a new text layout.
7107
7108 int oldht = mLayout.getHeight();
7109 int want = mLayout.getWidth();
7110 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7111
7112 /*
7113 * No need to bring the text into view, since the size is not
7114 * changing (unless we do the requestLayout(), in which case it
7115 * will happen at measure).
7116 */
7117 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08007118 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7119 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007120
Romain Guye1e0dc82009-11-03 17:21:04 -08007121 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7122 // In a fixed-height view, so use our new text layout.
7123 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08007124 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08007125 invalidate();
7126 return;
7127 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007128
Romain Guye1e0dc82009-11-03 17:21:04 -08007129 // Dynamic height, but height has stayed the same,
7130 // so use our new text layout.
7131 if (mLayout.getHeight() == oldht &&
7132 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7133 invalidate();
7134 return;
7135 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007136 }
7137
7138 // We lose: the height has changed and we have a dynamic height.
7139 // Request a new view layout using our new text layout.
7140 requestLayout();
7141 invalidate();
7142 } else {
7143 // Dynamic width, so we have no choice but to request a new
7144 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007145 nullLayouts();
7146 requestLayout();
7147 invalidate();
7148 }
7149 }
7150
Gilles Debunne954325e2012-01-25 11:57:06 -08007151 @Override
7152 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7153 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07007154 if (mDeferScroll >= 0) {
7155 int curs = mDeferScroll;
7156 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07007157 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07007158 }
Gilles Debunne954325e2012-01-25 11:57:06 -08007159 }
7160
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007161 private boolean isShowingHint() {
7162 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7163 }
7164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007165 /**
7166 * Returns true if anything changed.
7167 */
7168 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007169 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007170 int line = 0;
7171 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007172 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007173 }
7174
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007175 Layout.Alignment a = layout.getParagraphAlignment(line);
7176 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007177 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7178 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007179 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007180
7181 int scrollx, scrolly;
7182
Doug Felt25b9f422011-07-11 13:48:37 -07007183 // Convert to left, center, or right alignment.
7184 if (a == Layout.Alignment.ALIGN_NORMAL) {
7185 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7186 Layout.Alignment.ALIGN_RIGHT;
7187 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7188 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7189 Layout.Alignment.ALIGN_LEFT;
7190 }
7191
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007192 if (a == Layout.Alignment.ALIGN_CENTER) {
7193 /*
7194 * Keep centered if possible, or, if it is too wide to fit,
7195 * keep leading edge in view.
7196 */
7197
Neil Fuller33253a42014-10-01 11:55:10 +01007198 int left = (int) Math.floor(layout.getLineLeft(line));
7199 int right = (int) Math.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007200
7201 if (right - left < hspace) {
7202 scrollx = (right + left) / 2 - hspace / 2;
7203 } else {
7204 if (dir < 0) {
7205 scrollx = right - hspace;
7206 } else {
7207 scrollx = left;
7208 }
7209 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007210 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Neil Fuller33253a42014-10-01 11:55:10 +01007211 int right = (int) Math.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07007212 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007213 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Neil Fuller33253a42014-10-01 11:55:10 +01007214 scrollx = (int) Math.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007215 }
7216
7217 if (ht < vspace) {
7218 scrolly = 0;
7219 } else {
7220 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7221 scrolly = ht - vspace;
7222 } else {
7223 scrolly = 0;
7224 }
7225 }
7226
7227 if (scrollx != mScrollX || scrolly != mScrollY) {
7228 scrollTo(scrollx, scrolly);
7229 return true;
7230 } else {
7231 return false;
7232 }
7233 }
7234
7235 /**
7236 * Move the point, specified by the offset, into the view if it is needed.
7237 * This has to be called after layout. Returns true if anything changed.
7238 */
7239 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07007240 if (isLayoutRequested()) {
7241 mDeferScroll = offset;
7242 return false;
7243 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007244 boolean changed = false;
7245
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007246 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07007247
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007248 if (layout == null) return changed;
7249
7250 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007251
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007252 int grav;
7253
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007254 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07007255 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007256 grav = 1;
7257 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007258 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007259 grav = -1;
7260 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007261 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007262 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007263 break;
7264 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007265 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007266 break;
7267 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007268 default:
7269 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07007270 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007271 }
7272
Raph Levienafe8e9b2012-12-19 16:09:32 -08007273 // We only want to clamp the cursor to fit within the layout width
7274 // in left-to-right modes, because in a right to left alignment,
7275 // we want to scroll to keep the line-right on the screen, as other
7276 // lines are likely to have text flush with the right margin, which
7277 // we want to keep visible.
7278 // A better long-term solution would probably be to measure both
7279 // the full line and a blank-trimmed version, and, for example, use
7280 // the latter measurement for centering and right alignment, but for
7281 // the time being we only implement the cursor clamping in left to
7282 // right where it is most likely to be annoying.
7283 final boolean clamped = grav > 0;
7284 // FIXME: Is it okay to truncate this, or should we round?
7285 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7286 final int top = layout.getLineTop(line);
7287 final int bottom = layout.getLineTop(line + 1);
7288
Neil Fuller33253a42014-10-01 11:55:10 +01007289 int left = (int) Math.floor(layout.getLineLeft(line));
7290 int right = (int) Math.ceil(layout.getLineRight(line));
Raph Levienafe8e9b2012-12-19 16:09:32 -08007291 int ht = layout.getHeight();
7292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007293 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7294 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08007295 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7296 // If cursor has been clamped, make sure we don't scroll.
7297 right = Math.max(x, left + hspace);
7298 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007299
7300 int hslack = (bottom - top) / 2;
7301 int vslack = hslack;
7302
7303 if (vslack > vspace / 4)
7304 vslack = vspace / 4;
7305 if (hslack > hspace / 4)
7306 hslack = hspace / 4;
7307
7308 int hs = mScrollX;
7309 int vs = mScrollY;
7310
7311 if (top - vs < vslack)
7312 vs = top - vslack;
7313 if (bottom - vs > vspace - vslack)
7314 vs = bottom - (vspace - vslack);
7315 if (ht - vs < vspace)
7316 vs = ht - vspace;
7317 if (0 - vs > 0)
7318 vs = 0;
7319
7320 if (grav != 0) {
7321 if (x - hs < hslack) {
7322 hs = x - hslack;
7323 }
7324 if (x - hs > hspace - hslack) {
7325 hs = x - (hspace - hslack);
7326 }
7327 }
7328
7329 if (grav < 0) {
7330 if (left - hs > 0)
7331 hs = left;
7332 if (right - hs < hspace)
7333 hs = right - hspace;
7334 } else if (grav > 0) {
7335 if (right - hs < hspace)
7336 hs = right - hspace;
7337 if (left - hs > 0)
7338 hs = left;
7339 } else /* grav == 0 */ {
7340 if (right - left <= hspace) {
7341 /*
7342 * If the entire text fits, center it exactly.
7343 */
7344 hs = left - (hspace - (right - left)) / 2;
7345 } else if (x > right - hslack) {
7346 /*
7347 * If we are near the right edge, keep the right edge
7348 * at the edge of the view.
7349 */
7350 hs = right - hspace;
7351 } else if (x < left + hslack) {
7352 /*
7353 * If we are near the left edge, keep the left edge
7354 * at the edge of the view.
7355 */
7356 hs = left;
7357 } else if (left > hs) {
7358 /*
7359 * Is there whitespace visible at the left? Fix it if so.
7360 */
7361 hs = left;
7362 } else if (right < hs + hspace) {
7363 /*
7364 * Is there whitespace visible at the right? Fix it if so.
7365 */
7366 hs = right - hspace;
7367 } else {
7368 /*
7369 * Otherwise, float as needed.
7370 */
7371 if (x - hs < hslack) {
7372 hs = x - hslack;
7373 }
7374 if (x - hs > hspace - hslack) {
7375 hs = x - (hspace - hslack);
7376 }
7377 }
7378 }
7379
7380 if (hs != mScrollX || vs != mScrollY) {
7381 if (mScroller == null) {
7382 scrollTo(hs, vs);
7383 } else {
7384 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7385 int dx = hs - mScrollX;
7386 int dy = vs - mScrollY;
7387
7388 if (duration > ANIMATED_SCROLL_GAP) {
7389 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07007390 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007391 invalidate();
7392 } else {
7393 if (!mScroller.isFinished()) {
7394 mScroller.abortAnimation();
7395 }
7396
7397 scrollBy(dx, dy);
7398 }
7399
7400 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7401 }
7402
7403 changed = true;
7404 }
7405
7406 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08007407 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7408 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007409
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007410 // The offsets here are to ensure the rectangle we are using is
7411 // within our view bounds, in case the cursor is on the far left
7412 // or right. If it isn't withing the bounds, then this request
7413 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08007414 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007415 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08007416 getInterestingRect(mTempRect, line);
7417 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007418
Gilles Debunne716dbf62011-03-07 18:12:10 -08007419 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007420 changed = true;
7421 }
7422 }
7423
7424 return changed;
7425 }
7426
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007427 /**
7428 * Move the cursor, if needed, so that it is at an offset that is visible
7429 * to the user. This will not move the cursor if it represents more than
7430 * one character (a selection range). This will only work if the
7431 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007432 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07007433 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007434 */
7435 public boolean moveCursorToVisibleOffset() {
7436 if (!(mText instanceof Spannable)) {
7437 return false;
7438 }
Gilles Debunne05336272010-07-09 20:13:45 -07007439 int start = getSelectionStart();
7440 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007441 if (start != end) {
7442 return false;
7443 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007444
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007445 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007446
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007447 int line = mLayout.getLineForOffset(start);
7448
7449 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007450 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007451 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7452 int vslack = (bottom - top) / 2;
7453 if (vslack > vspace / 4)
7454 vslack = vspace / 4;
7455 final int vs = mScrollY;
7456
7457 if (top < (vs+vslack)) {
7458 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7459 } else if (bottom > (vspace+vs-vslack)) {
7460 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7461 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007462
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007463 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007464
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007465 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7466 final int hs = mScrollX;
7467 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7468 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07007469
Doug Feltc982f602010-05-25 11:51:40 -07007470 // line might contain bidirectional text
7471 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7472 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7473
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007474 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07007475 if (newStart < lowChar) {
7476 newStart = lowChar;
7477 } else if (newStart > highChar) {
7478 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007479 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007480
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007481 if (newStart != start) {
7482 Selection.setSelection((Spannable)mText, newStart);
7483 return true;
7484 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007485
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007486 return false;
7487 }
7488
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007489 @Override
7490 public void computeScroll() {
7491 if (mScroller != null) {
7492 if (mScroller.computeScrollOffset()) {
7493 mScrollX = mScroller.getCurrX();
7494 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08007495 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007496 postInvalidate(); // So we draw again
7497 }
7498 }
7499 }
7500
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007501 private void getInterestingRect(Rect r, int line) {
7502 convertFromViewportToContentCoordinates(r);
7503
7504 // Rectangle can can be expanded on first and last line to take
7505 // padding into account.
7506 // TODO Take left/right padding into account too?
7507 if (line == 0) r.top -= getExtendedPaddingTop();
7508 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7509 }
7510
7511 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007512 final int horizontalOffset = viewportToContentHorizontalOffset();
7513 r.left += horizontalOffset;
7514 r.right += horizontalOffset;
7515
7516 final int verticalOffset = viewportToContentVerticalOffset();
7517 r.top += verticalOffset;
7518 r.bottom += verticalOffset;
7519 }
7520
Gilles Debunned88876a2012-03-16 17:34:04 -07007521 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007522 return getCompoundPaddingLeft() - mScrollX;
7523 }
7524
Gilles Debunned88876a2012-03-16 17:34:04 -07007525 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007526 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007527 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007528 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007529 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007530 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007531 }
7532
7533 @Override
7534 public void debug(int depth) {
7535 super.debug(depth);
7536
7537 String output = debugIndent(depth);
7538 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7539 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7540 + "} ";
7541
7542 if (mText != null) {
7543
7544 output += "mText=\"" + mText + "\" ";
7545 if (mLayout != null) {
7546 output += "mLayout width=" + mLayout.getWidth()
7547 + " height=" + mLayout.getHeight();
7548 }
7549 } else {
7550 output += "mText=NULL";
7551 }
7552 Log.d(VIEW_LOG_TAG, output);
7553 }
7554
7555 /**
7556 * Convenience for {@link Selection#getSelectionStart}.
7557 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007558 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007559 public int getSelectionStart() {
7560 return Selection.getSelectionStart(getText());
7561 }
7562
7563 /**
7564 * Convenience for {@link Selection#getSelectionEnd}.
7565 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007566 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007567 public int getSelectionEnd() {
7568 return Selection.getSelectionEnd(getText());
7569 }
7570
7571 /**
7572 * Return true iff there is a selection inside this text view.
7573 */
7574 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007575 final int selectionStart = getSelectionStart();
7576 final int selectionEnd = getSelectionEnd();
7577
7578 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007579 }
7580
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007581 String getSelectedText() {
7582 if (hasSelection()) {
7583 return String.valueOf(mText.subSequence(getSelectionStart(), getSelectionEnd()));
7584 }
7585 return null;
7586 }
7587
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007588 /**
7589 * Sets the properties of this field (lines, horizontally scrolling,
7590 * transformation method) to be for a single-line input.
7591 *
7592 * @attr ref android.R.styleable#TextView_singleLine
7593 */
7594 public void setSingleLine() {
7595 setSingleLine(true);
7596 }
7597
7598 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007599 * Sets the properties of this field to transform input to ALL CAPS
7600 * display. This may use a "small caps" formatting if available.
7601 * This setting will be ignored if this field is editable or selectable.
7602 *
7603 * This call replaces the current transformation method. Disabling this
7604 * will not necessarily restore the previous behavior from before this
7605 * was enabled.
7606 *
7607 * @see #setTransformationMethod(TransformationMethod)
7608 * @attr ref android.R.styleable#TextView_textAllCaps
7609 */
7610 public void setAllCaps(boolean allCaps) {
7611 if (allCaps) {
7612 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7613 } else {
7614 setTransformationMethod(null);
7615 }
7616 }
7617
7618 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007619 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7620 * transformation method) to be for a single-line input; if false, restores these to the default
7621 * conditions.
7622 *
7623 * Note that the default conditions are not necessarily those that were in effect prior this
7624 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007625 *
7626 * @attr ref android.R.styleable#TextView_singleLine
7627 */
7628 @android.view.RemotableViewMethod
7629 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007630 // Could be used, but may break backward compatibility.
7631 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007632 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007633 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007634 }
7635
7636 /**
7637 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7638 * @param singleLine
7639 */
7640 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007641 if (mEditor != null &&
7642 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007643 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007644 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007645 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007646 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007647 }
7648 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007649 }
7650
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007651 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7652 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007653 mSingleLine = singleLine;
7654 if (singleLine) {
7655 setLines(1);
7656 setHorizontallyScrolling(true);
7657 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007658 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007659 }
7660 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007661 if (changeMaxLines) {
7662 setMaxLines(Integer.MAX_VALUE);
7663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007664 setHorizontallyScrolling(false);
7665 if (applyTransformation) {
7666 setTransformationMethod(null);
7667 }
7668 }
7669 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007671 /**
7672 * Causes words in the text that are longer than the view is wide
7673 * to be ellipsized instead of broken in the middle. You may also
7674 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007675 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007676 * to turn off ellipsizing.
7677 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007678 * If {@link #setMaxLines} has been used to set two or more lines,
Newton Allen8f8a11b2013-11-26 10:25:38 -08007679 * only {@link android.text.TextUtils.TruncateAt#END} and
7680 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
Gilles Debunne6435a562011-08-04 21:22:30 -07007681 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007682 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007683 * @attr ref android.R.styleable#TextView_ellipsize
7684 */
7685 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007686 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7687 if (mEllipsize != where) {
7688 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007689
Gilles Debunne22378292011-08-12 10:38:52 -07007690 if (mLayout != null) {
7691 nullLayouts();
7692 requestLayout();
7693 invalidate();
7694 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007695 }
7696 }
7697
7698 /**
7699 * Sets how many times to repeat the marquee animation. Only applied if the
7700 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7701 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007702 * @see #getMarqueeRepeatLimit()
7703 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007704 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7705 */
7706 public void setMarqueeRepeatLimit(int marqueeLimit) {
7707 mMarqueeRepeatLimit = marqueeLimit;
7708 }
7709
7710 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007711 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7712 * TextView has marquee enabled.
7713 *
7714 * @return the number of times the marquee animation is repeated. -1 if the animation
7715 * repeats indefinitely
7716 *
7717 * @see #setMarqueeRepeatLimit(int)
7718 *
7719 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7720 */
7721 public int getMarqueeRepeatLimit() {
7722 return mMarqueeRepeatLimit;
7723 }
7724
7725 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007726 * Returns where, if anywhere, words that are longer than the view
7727 * is wide should be ellipsized.
7728 */
7729 @ViewDebug.ExportedProperty
7730 public TextUtils.TruncateAt getEllipsize() {
7731 return mEllipsize;
7732 }
7733
7734 /**
7735 * Set the TextView so that when it takes focus, all the text is
7736 * selected.
7737 *
7738 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7739 */
7740 @android.view.RemotableViewMethod
7741 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007742 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007743 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007744
7745 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7746 setText(mText, BufferType.SPANNABLE);
7747 }
7748 }
7749
7750 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007751 * Set whether the cursor is visible. The default is true. Note that this property only
7752 * makes sense for editable TextView.
7753 *
7754 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007755 *
7756 * @attr ref android.R.styleable#TextView_cursorVisible
7757 */
7758 @android.view.RemotableViewMethod
7759 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007760 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007761 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007762 if (mEditor.mCursorVisible != visible) {
7763 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007764 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007765
Gilles Debunne2d373a12012-04-20 15:32:19 -07007766 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007767
Gilles Debunne3d010062011-02-18 14:16:41 -08007768 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007769 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007770 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007771 }
7772
Gilles Debunnef03acef2012-04-30 19:26:19 -07007773 /**
7774 * @return whether or not the cursor is visible (assuming this TextView is editable)
7775 *
7776 * @see #setCursorVisible(boolean)
7777 *
7778 * @attr ref android.R.styleable#TextView_cursorVisible
7779 */
7780 public boolean isCursorVisible() {
7781 // true is the default value
7782 return mEditor == null ? true : mEditor.mCursorVisible;
7783 }
7784
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007785 private boolean canMarquee() {
7786 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007787 return width > 0 && (mLayout.getLineWidth(0) > width ||
7788 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7789 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007790 }
7791
7792 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007793 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007794 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007795
Romain Guy939151f2009-04-08 14:22:40 -07007796 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7797 return;
7798 }
7799
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007800 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7801 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007802
Adam Powell282e3772011-08-30 16:51:11 -07007803 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7804 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7805 final Layout tmp = mLayout;
7806 mLayout = mSavedMarqueeModeLayout;
7807 mSavedMarqueeModeLayout = tmp;
7808 setHorizontalFadingEdgeEnabled(true);
7809 requestLayout();
7810 invalidate();
7811 }
7812
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007813 if (mMarquee == null) mMarquee = new Marquee(this);
7814 mMarquee.start(mMarqueeRepeatLimit);
7815 }
7816 }
7817
7818 private void stopMarquee() {
7819 if (mMarquee != null && !mMarquee.isStopped()) {
7820 mMarquee.stop();
7821 }
Adam Powell282e3772011-08-30 16:51:11 -07007822
7823 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7824 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7825 final Layout tmp = mSavedMarqueeModeLayout;
7826 mSavedMarqueeModeLayout = mLayout;
7827 mLayout = tmp;
7828 setHorizontalFadingEdgeEnabled(false);
7829 requestLayout();
7830 invalidate();
7831 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007832 }
7833
7834 private void startStopMarquee(boolean start) {
7835 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7836 if (start) {
7837 startMarquee();
7838 } else {
7839 stopMarquee();
7840 }
7841 }
7842 }
7843
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007844 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007845 * This method is called when the text is changed, in case any subclasses
7846 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007847 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007848 * Within <code>text</code>, the <code>lengthAfter</code> characters
7849 * beginning at <code>start</code> have just replaced old text that had
7850 * length <code>lengthBefore</code>. It is an error to attempt to make
7851 * changes to <code>text</code> from this callback.
7852 *
7853 * @param text The text the TextView is displaying
7854 * @param start The offset of the start of the range of the text that was
7855 * modified
7856 * @param lengthBefore The length of the former text that has been replaced
7857 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007858 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007859 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007860 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007861 }
7862
7863 /**
7864 * This method is called when the selection has changed, in case any
7865 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007866 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007867 * @param selStart The new selection start location.
7868 * @param selEnd The new selection end location.
7869 */
7870 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007871 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007872 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007873
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007874 /**
7875 * Adds a TextWatcher to the list of those whose methods are called
7876 * whenever this TextView's text changes.
7877 * <p>
7878 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7879 * not called after {@link #setText} calls. Now, doing {@link #setText}
7880 * if there are any text changed listeners forces the buffer type to
7881 * Editable if it would not otherwise be and does call this method.
7882 */
7883 public void addTextChangedListener(TextWatcher watcher) {
7884 if (mListeners == null) {
7885 mListeners = new ArrayList<TextWatcher>();
7886 }
7887
7888 mListeners.add(watcher);
7889 }
7890
7891 /**
7892 * Removes the specified TextWatcher from the list of those whose
7893 * methods are called
7894 * whenever this TextView's text changes.
7895 */
7896 public void removeTextChangedListener(TextWatcher watcher) {
7897 if (mListeners != null) {
7898 int i = mListeners.indexOf(watcher);
7899
7900 if (i >= 0) {
7901 mListeners.remove(i);
7902 }
7903 }
7904 }
7905
Gilles Debunne6435a562011-08-04 21:22:30 -07007906 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007907 if (mListeners != null) {
7908 final ArrayList<TextWatcher> list = mListeners;
7909 final int count = list.size();
7910 for (int i = 0; i < count; i++) {
7911 list.get(i).beforeTextChanged(text, start, before, after);
7912 }
7913 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007914
7915 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007916 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7917 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07007918 }
7919
7920 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007921 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007922 if (!(mText instanceof Editable)) return;
7923 Editable text = (Editable) mText;
7924
7925 T[] spans = text.getSpans(start, end, type);
7926 final int length = spans.length;
7927 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007928 final int spanStart = text.getSpanStart(spans[i]);
7929 final int spanEnd = text.getSpanEnd(spans[i]);
7930 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07007931 text.removeSpan(spans[i]);
7932 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007933 }
7934
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007935 void removeAdjacentSuggestionSpans(final int pos) {
7936 if (!(mText instanceof Editable)) return;
7937 final Editable text = (Editable) mText;
7938
7939 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7940 final int length = spans.length;
7941 for (int i = 0; i < length; i++) {
7942 final int spanStart = text.getSpanStart(spans[i]);
7943 final int spanEnd = text.getSpanEnd(spans[i]);
7944 if (spanEnd == pos || spanStart == pos) {
7945 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7946 text.removeSpan(spans[i]);
7947 }
7948 }
7949 }
7950 }
7951
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007952 /**
7953 * Not private so it can be called from an inner class without going
7954 * through a thunk.
7955 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007956 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007957 if (mListeners != null) {
7958 final ArrayList<TextWatcher> list = mListeners;
7959 final int count = list.size();
7960 for (int i = 0; i < count; i++) {
7961 list.get(i).onTextChanged(text, start, before, after);
7962 }
7963 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007964
Gilles Debunne2d373a12012-04-20 15:32:19 -07007965 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007966 }
7967
7968 /**
7969 * Not private so it can be called from an inner class without going
7970 * through a thunk.
7971 */
7972 void sendAfterTextChanged(Editable text) {
7973 if (mListeners != null) {
7974 final ArrayList<TextWatcher> list = mListeners;
7975 final int count = list.size();
7976 for (int i = 0; i < count; i++) {
7977 list.get(i).afterTextChanged(text);
7978 }
7979 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07007980 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007981 }
7982
Gilles Debunned88876a2012-03-16 17:34:04 -07007983 void updateAfterEdit() {
7984 invalidate();
7985 int curs = getSelectionStart();
7986
7987 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7988 registerForPreDraw();
7989 }
7990
Raph Levienf5c1a872012-10-15 17:22:26 -07007991 checkForResize();
7992
Gilles Debunned88876a2012-03-16 17:34:04 -07007993 if (curs >= 0) {
7994 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007995 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007996 bringPointIntoView(curs);
7997 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007998 }
7999
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008000 /**
8001 * Not private so it can be called from an inner class without going
8002 * through a thunk.
8003 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008004 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008005 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008006 if (ims == null || ims.mBatchEditNesting == 0) {
8007 updateAfterEdit();
8008 }
8009 if (ims != null) {
8010 ims.mContentChanged = true;
8011 if (ims.mChangedStart < 0) {
8012 ims.mChangedStart = start;
8013 ims.mChangedEnd = start+before;
8014 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01008015 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8016 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008017 }
8018 ims.mChangedDelta += after-before;
8019 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07008020 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008021 sendOnTextChanged(buffer, start, before, after);
8022 onTextChanged(buffer, start, before, after);
8023 }
Gilles Debunne60e21862012-01-30 15:04:14 -08008024
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008025 /**
8026 * Not private so it can be called from an inner class without going
8027 * through a thunk.
8028 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008029 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008030 // XXX Make the start and end move together if this ends up
8031 // spending too much time invalidating.
8032
8033 boolean selChanged = false;
8034 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008035
Gilles Debunne2d373a12012-04-20 15:32:19 -07008036 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008037
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008038 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008039 selChanged = true;
8040 newSelEnd = newStart;
8041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008042 if (oldStart >= 0 || newStart >= 0) {
8043 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07008044 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008045 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008046 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008047 }
8048 }
8049
8050 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008051 selChanged = true;
8052 newSelStart = newStart;
8053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008054 if (oldStart >= 0 || newStart >= 0) {
8055 int end = Selection.getSelectionEnd(buf);
8056 invalidateCursor(end, oldStart, newStart);
8057 }
8058 }
8059
8060 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008061 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008062 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08008063
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008064 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8065 if (newSelStart < 0) {
8066 newSelStart = Selection.getSelectionStart(buf);
8067 }
8068 if (newSelEnd < 0) {
8069 newSelEnd = Selection.getSelectionEnd(buf);
8070 }
8071 onSelectionChanged(newSelStart, newSelEnd);
8072 }
8073 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08008074
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08008075 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8076 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008077 if (ims == null || ims.mBatchEditNesting == 0) {
8078 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08008079 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008080 checkForResize();
8081 } else {
8082 ims.mContentChanged = true;
8083 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07008084 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008085 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8086 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07008087 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008088 }
8089
8090 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008091 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008092 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8093 ims.mSelectionModeChanged = true;
8094 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008095
8096 if (Selection.getSelectionStart(buf) >= 0) {
8097 if (ims == null || ims.mBatchEditNesting == 0) {
8098 invalidateCursor();
8099 } else {
8100 ims.mCursorChanged = true;
8101 }
8102 }
8103 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008105 if (what instanceof ParcelableSpan) {
8106 // If this is a span that can be sent to a remote process,
8107 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07008108 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008109 if (ims.mBatchEditNesting != 0) {
8110 if (oldStart >= 0) {
8111 if (ims.mChangedStart > oldStart) {
8112 ims.mChangedStart = oldStart;
8113 }
8114 if (ims.mChangedStart > oldEnd) {
8115 ims.mChangedStart = oldEnd;
8116 }
8117 }
8118 if (newStart >= 0) {
8119 if (ims.mChangedStart > newStart) {
8120 ims.mChangedStart = newStart;
8121 }
8122 if (ims.mChangedStart > newEnd) {
8123 ims.mChangedStart = newEnd;
8124 }
8125 }
8126 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008127 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008128 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07008129 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008130 ims.mContentChanged = true;
8131 }
8132 }
8133 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008134
Gilles Debunne2d373a12012-04-20 15:32:19 -07008135 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8136 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07008137 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07008138 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008139 }
8140
Gilles Debunne6435a562011-08-04 21:22:30 -07008141 /**
Romain Guydcc490f2010-02-24 17:59:35 -08008142 * @hide
8143 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008144 @Override
Romain Guya440b002010-02-24 15:57:54 -08008145 public void dispatchFinishTemporaryDetach() {
8146 mDispatchTemporaryDetach = true;
8147 super.dispatchFinishTemporaryDetach();
8148 mDispatchTemporaryDetach = false;
8149 }
8150
8151 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008152 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008153 super.onStartTemporaryDetach();
8154 // Only track when onStartTemporaryDetach() is called directly,
8155 // usually because this instance is an editable field in a list
8156 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08008157
Adam Powell057a5852012-05-11 10:28:38 -07008158 // Tell the editor that we are temporarily detached. It can use this to preserve
8159 // selection state as needed.
8160 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008161 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008163 @Override
8164 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008165 super.onFinishTemporaryDetach();
8166 // Only track when onStartTemporaryDetach() is called directly,
8167 // usually because this instance is an editable field in a list
8168 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07008169 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008170 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008171
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008172 @Override
8173 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8174 if (mTemporaryDetach) {
8175 // If we are temporarily in the detach state, then do nothing.
8176 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8177 return;
8178 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008179
Gilles Debunne2d373a12012-04-20 15:32:19 -07008180 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07008181
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008182 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008183 if (mText instanceof Spannable) {
8184 Spannable sp = (Spannable) mText;
8185 MetaKeyKeyListener.resetMetaState(sp);
8186 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008187 }
8188
8189 startStopMarquee(focused);
8190
8191 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008192 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008193 }
8194
8195 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8196 }
8197
8198 @Override
8199 public void onWindowFocusChanged(boolean hasWindowFocus) {
8200 super.onWindowFocusChanged(hasWindowFocus);
8201
Gilles Debunne2d373a12012-04-20 15:32:19 -07008202 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008203
8204 startStopMarquee(hasWindowFocus);
8205 }
8206
Adam Powellba0a2c32010-09-28 17:41:23 -07008207 @Override
8208 protected void onVisibilityChanged(View changedView, int visibility) {
8209 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08008210 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008211 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07008212 }
8213 }
8214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008215 /**
8216 * Use {@link BaseInputConnection#removeComposingSpans
8217 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8218 * state from this text view.
8219 */
8220 public void clearComposingText() {
8221 if (mText instanceof Spannable) {
8222 BaseInputConnection.removeComposingSpans((Spannable)mText);
8223 }
8224 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008225
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008226 @Override
8227 public void setSelected(boolean selected) {
8228 boolean wasSelected = isSelected();
8229
8230 super.setSelected(selected);
8231
8232 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8233 if (selected) {
8234 startMarquee();
8235 } else {
8236 stopMarquee();
8237 }
8238 }
8239 }
8240
8241 @Override
8242 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008243 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07008244
Mady Mellor2ff2cd82015-03-02 10:37:01 -08008245 if (mEditor != null) {
8246 mEditor.onTouchEvent(event);
8247
8248 if (mEditor.mSelectionModifierCursorController != null &&
8249 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8250 return true;
8251 }
8252 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008254 final boolean superResult = super.onTouchEvent(event);
8255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008256 /*
8257 * Don't handle the release after a long press, because it will
8258 * move the selection away from whatever the menu action was
8259 * trying to affect.
8260 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07008261 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8262 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008263 return superResult;
8264 }
8265
Gilles Debunne70a63122011-09-01 13:27:33 -07008266 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008267 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08008268
Gilles Debunne70a63122011-09-01 13:27:33 -07008269 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03008270 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008271 boolean handled = false;
8272
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07008273 if (mMovement != null) {
8274 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8275 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008276
Gilles Debunne60e21862012-01-30 15:04:14 -08008277 final boolean textIsSelectable = isTextSelectable();
8278 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008279 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07008280 // on non editable text that support text selection.
8281 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008282 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8283 getSelectionEnd(), ClickableSpan.class);
8284
Gilles Debunne822b8f02012-01-17 18:02:15 -08008285 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008286 links[0].onClick(this);
8287 handled = true;
8288 }
8289 }
8290
Gilles Debunne60e21862012-01-30 15:04:14 -08008291 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008292 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09008293 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09008294 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07008295 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008296 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07008297 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008298
Gilles Debunned88876a2012-03-16 17:34:04 -07008299 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07008300 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07008301
8302 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008303 }
8304
The Android Open Source Project4df24232009-03-05 14:34:35 -08008305 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008306 return true;
8307 }
8308 }
8309
8310 return superResult;
8311 }
8312
Jeff Brown8f345672011-02-26 13:29:53 -08008313 @Override
8314 public boolean onGenericMotionEvent(MotionEvent event) {
8315 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8316 try {
8317 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8318 return true;
8319 }
8320 } catch (AbstractMethodError ex) {
8321 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8322 // Ignore its absence in case third party applications implemented the
8323 // interface directly.
8324 }
8325 }
8326 return super.onGenericMotionEvent(event);
8327 }
8328
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008329 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08008330 * @return True iff this TextView contains a text that can be edited, or if this is
8331 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008332 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008333 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08008334 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008335 }
8336
The Android Open Source Project4df24232009-03-05 14:34:35 -08008337 /**
8338 * Returns true, only while processing a touch gesture, if the initial
8339 * touch down event caused focus to move to the text view and as a result
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008340 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07008341 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08008342 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008343 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008344 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08008345 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008346
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008347 @Override
8348 public void cancelLongPress() {
8349 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008350 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008351 }
Gilles Debunne70a63122011-09-01 13:27:33 -07008352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008353 @Override
8354 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008355 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008356 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8357 return true;
8358 }
8359 }
8360
8361 return super.onTrackballEvent(event);
8362 }
8363
8364 public void setScroller(Scroller s) {
8365 mScroller = s;
8366 }
8367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008368 @Override
8369 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008370 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8371 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008372 if (mMarquee != null && !mMarquee.isStopped()) {
8373 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07008374 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008375 final float scroll = marquee.getScroll();
8376 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07008377 } else {
8378 return 0.0f;
8379 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008380 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008381 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008382 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008383 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008384 case Gravity.LEFT:
8385 return 0.0f;
8386 case Gravity.RIGHT:
8387 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8388 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8389 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8390 case Gravity.CENTER_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008391 case Gravity.FILL_HORIZONTAL:
8392 final int textDirection = mLayout.getParagraphDirection(0);
8393 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8394 return 0.0f;
8395 } else {
8396 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8397 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8398 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8399 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008400 }
8401 }
8402 }
8403 return super.getLeftFadingEdgeStrength();
8404 }
8405
8406 @Override
8407 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008408 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8409 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008410 if (mMarquee != null && !mMarquee.isStopped()) {
8411 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008412 final float maxFadeScroll = marquee.getMaxFadeScroll();
8413 final float scroll = marquee.getScroll();
8414 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008415 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008416 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008417 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008418 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008419 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07008420 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8421 getCompoundPaddingRight();
8422 final float lineWidth = mLayout.getLineWidth(0);
8423 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008424 case Gravity.RIGHT:
8425 return 0.0f;
8426 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07008427 case Gravity.FILL_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008428 final int textDirection = mLayout.getParagraphDirection(0);
8429 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8430 return 0.0f;
8431 } else {
8432 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008433 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8434 getHorizontalFadingEdgeLength();
Raph Levien8079ae12013-09-26 15:57:07 -07008435 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008436 }
8437 }
8438 }
8439 return super.getRightFadingEdgeStrength();
8440 }
8441
8442 @Override
8443 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07008444 if (mLayout != null) {
8445 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8446 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8447 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008448
8449 return super.computeHorizontalScrollRange();
8450 }
8451
8452 @Override
8453 protected int computeVerticalScrollRange() {
8454 if (mLayout != null)
8455 return mLayout.getHeight();
8456
8457 return super.computeVerticalScrollRange();
8458 }
8459
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008460 @Override
8461 protected int computeVerticalScrollExtent() {
8462 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8463 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008464
8465 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07008466 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8467 super.findViewsWithText(outViews, searched, flags);
8468 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8469 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8470 String searchedLowerCase = searched.toString().toLowerCase();
8471 String textLowerCase = mText.toString().toLowerCase();
8472 if (textLowerCase.contains(searchedLowerCase)) {
8473 outViews.add(this);
8474 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008475 }
8476 }
8477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008478 public enum BufferType {
8479 NORMAL, SPANNABLE, EDITABLE,
8480 }
8481
8482 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008483 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8484 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8485 * if TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008486 *
8487 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008488 */
8489 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
Alan Viverettefe167872014-09-16 15:41:27 -07008490 if (attrs == null) {
8491 // Preserve behavior prior to removal of this API.
8492 throw new NullPointerException();
8493 }
8494
Alan Viveretteabda5482014-06-27 16:10:54 -07008495 // It's not safe to use this method from apps. The parameter 'attrs'
8496 // must have been obtained using the TextView filter array which is not
8497 // available to the SDK. As such, we grab a default TypedArray with the
8498 // right filter instead here.
8499 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8500 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008501 if (colors == null) {
Alan Viveretteabda5482014-06-27 16:10:54 -07008502 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8503 if (ap != 0) {
8504 final TypedArray appearance = context.obtainStyledAttributes(
8505 ap, R.styleable.TextAppearance);
8506 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008507 appearance.recycle();
8508 }
8509 }
Alan Viveretteabda5482014-06-27 16:10:54 -07008510 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008511
8512 return colors;
8513 }
8514
8515 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008516 * Returns the default color from the TextView_textColor attribute from the
8517 * AttributeSet, if set, or the default color from the
8518 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8519 * TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008520 *
8521 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008522 */
Alan Viveretteabda5482014-06-27 16:10:54 -07008523 public static int getTextColor(Context context, TypedArray attrs, int def) {
8524 final ColorStateList colors = getTextColors(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008525 if (colors == null) {
8526 return def;
8527 } else {
8528 return colors.getDefaultColor();
8529 }
8530 }
8531
8532 @Override
8533 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
James Cook9201e792015-02-11 10:46:44 -08008534 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8535 // Handle Ctrl-only shortcuts.
Jeff Brownc1df9072010-12-21 16:38:50 -08008536 switch (keyCode) {
8537 case KeyEvent.KEYCODE_A:
8538 if (canSelectText()) {
8539 return onTextContextMenuItem(ID_SELECT_ALL);
8540 }
8541 break;
James Cookf59152c2015-02-26 18:03:58 -08008542 case KeyEvent.KEYCODE_Z:
8543 if (canUndo()) {
8544 return onTextContextMenuItem(ID_UNDO);
8545 }
8546 break;
Jeff Brownc1df9072010-12-21 16:38:50 -08008547 case KeyEvent.KEYCODE_X:
8548 if (canCut()) {
8549 return onTextContextMenuItem(ID_CUT);
8550 }
8551 break;
8552 case KeyEvent.KEYCODE_C:
8553 if (canCopy()) {
8554 return onTextContextMenuItem(ID_COPY);
8555 }
8556 break;
8557 case KeyEvent.KEYCODE_V:
8558 if (canPaste()) {
8559 return onTextContextMenuItem(ID_PASTE);
8560 }
8561 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008562 }
James Cook9201e792015-02-11 10:46:44 -08008563 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8564 // Handle Ctrl-Shift shortcuts.
8565 switch (keyCode) {
James Cookf59152c2015-02-26 18:03:58 -08008566 case KeyEvent.KEYCODE_Z:
8567 if (canRedo()) {
8568 return onTextContextMenuItem(ID_REDO);
8569 }
8570 break;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09008571 case KeyEvent.KEYCODE_V:
8572 if (canPaste()) {
8573 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8574 }
James Cook9201e792015-02-11 10:46:44 -08008575 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008576 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008577 return super.onKeyShortcut(keyCode, event);
8578 }
8579
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008580 /**
8581 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8582 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07008583 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8584 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008585 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07008586 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008587 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008588 }
8589
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008590 /**
8591 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8592 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008593 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008594 * See also {@link #canSelectText()}.
8595 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008596 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008597 // prepareCursorController() relies on this method.
8598 // If you change this condition, make sure prepareCursorController is called anywhere
8599 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008600 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008601 return isTextEditable() ||
8602 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008603 }
8604
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008605 private Locale getTextServicesLocale(boolean allowNullLocale) {
8606 // Start fetching the text services locale asynchronously.
8607 updateTextServicesLocaleAsync();
8608 // If !allowNullLocale and there is no cached text services locale, just return the default
8609 // locale.
8610 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8611 : mCurrentSpellCheckerLocaleCache;
8612 }
8613
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008614 /**
8615 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008616 * Caveat: This method may not return the latest text services locale, but this should be
8617 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008618 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008619 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008620 * in this TextView, based on the current spell checker settings,
8621 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008622 * Please note that a word iterator in this TextView is different from another word iterator
8623 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008624 * @hide
8625 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008626 // TODO: Support multi-locale
8627 // TODO: Update the text services locale immediately after the keyboard locale is switched
8628 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008629 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008630 return getTextServicesLocale(false /* allowNullLocale */);
8631 }
8632
8633 /**
8634 * This is a temporary method. Future versions may support multi-locale text.
8635 * Caveat: This method may not return the latest spell checker locale, but this should be
8636 * acceptable and it's more important to make this method asynchronous.
8637 *
8638 * @return The locale that should be used for a spell checker in this TextView,
8639 * based on the current spell checker settings, the current IME's locale, or the system default
8640 * locale.
8641 * @hide
8642 */
8643 public Locale getSpellCheckerLocale() {
8644 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008645 }
8646
8647 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008648 // AsyncTask.execute() uses a serial executor which means we don't have
8649 // to lock around updateTextServicesLocaleLocked() to prevent it from
8650 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008651 AsyncTask.execute(new Runnable() {
8652 @Override
8653 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008654 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008655 }
8656 });
8657 }
8658
8659 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008660 final TextServicesManager textServicesManager = (TextServicesManager)
8661 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8662 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008663 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008664 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09008665 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008666 } else {
8667 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008668 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008669 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008670 }
8671
8672 void onLocaleChanged() {
8673 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008674 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008675 }
8676
8677 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008678 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8679 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008680 * @hide
8681 */
8682 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008683 if (mEditor != null) {
8684 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008685 } else {
8686 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008687 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008688 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008689
Alan Viverettea54956a2015-01-07 16:05:02 -08008690 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008691 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008692 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8693 super.onPopulateAccessibilityEventInternal(event);
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008694
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08008695 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07008696 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8697 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07008698 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07008699 event.getText().add(text);
8700 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008701 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008702 }
8703
alanv7d624192012-05-21 14:23:17 -07008704 /**
8705 * @return true if the user has explicitly allowed accessibility services
8706 * to speak passwords.
8707 */
8708 private boolean shouldSpeakPasswordsForAccessibility() {
Alan Viveretteb6e0cb92014-11-24 15:13:43 -08008709 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
Alan Viverette97524282014-12-02 16:24:24 -08008710 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8711 UserHandle.USER_CURRENT_OR_SELF) == 1);
alanv7d624192012-05-21 14:23:17 -07008712 }
8713
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008714 @Override
8715 public CharSequence getAccessibilityClassName() {
8716 return TextView.class.getName();
8717 }
8718
8719 @Override
Dianne Hackborn6251f0d2015-04-01 16:45:03 -07008720 public void onProvideAssistStructure(ViewAssistStructure structure) {
8721 super.onProvideAssistStructure(structure);
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008722 final boolean isPassword = hasPasswordTransformationMethod();
8723 if (!isPassword) {
Dianne Hackborna83ce1d2015-03-11 15:16:13 -07008724 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8725 structure.setTextPaint(mTextPaint);
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008726 }
Dianne Hackborna83ce1d2015-03-11 15:16:13 -07008727 structure.setHint(getHint());
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008728 }
8729
Alan Viverettea54956a2015-01-07 16:05:02 -08008730 /** @hide */
Svetoslav Ganov30401322011-05-12 18:53:45 -07008731 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008732 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
8733 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov30401322011-05-12 18:53:45 -07008734
8735 final boolean isPassword = hasPasswordTransformationMethod();
8736 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07008737
8738 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8739 event.setFromIndex(Selection.getSelectionStart(mText));
8740 event.setToIndex(Selection.getSelectionEnd(mText));
8741 event.setItemCount(mText.length());
8742 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07008743 }
8744
Alan Viverettea54956a2015-01-07 16:05:02 -08008745 /** @hide */
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008746 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008747 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
8748 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008749
8750 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008751 info.setPassword(isPassword);
8752
Alan Viverette3a499e02014-05-19 16:04:40 -07008753 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008754 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008755 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008756
Svetoslavbcc46a02013-02-06 11:56:00 -08008757 if (mBufferType == BufferType.EDITABLE) {
8758 info.setEditable(true);
8759 }
8760
Svetoslav6254f482013-06-04 17:22:14 -07008761 if (mEditor != null) {
8762 info.setInputType(mEditor.mInputType);
Alan Viverette5b2081d2013-08-28 10:43:07 -07008763
8764 if (mEditor.mError != null) {
8765 info.setContentInvalid(true);
Alan Viverettefccbff52014-07-07 15:06:14 -07008766 info.setError(mEditor.mError);
Alan Viverette5b2081d2013-08-28 10:43:07 -07008767 }
Svetoslav6254f482013-06-04 17:22:14 -07008768 }
8769
Svetoslavdb7da0e2013-04-22 18:34:02 -07008770 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008771 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8772 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8773 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8774 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8775 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8776 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8777 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8778 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008779
Svetoslav7c512842013-01-30 23:02:08 -08008780 if (isFocused()) {
8781 if (canSelectText()) {
8782 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8783 }
8784 if (canCopy()) {
8785 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8786 }
8787 if (canPaste()) {
8788 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8789 }
8790 if (canCut()) {
8791 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8792 }
8793 }
Alan Viverette058ac7c2013-08-19 16:44:30 -07008794
Alan Viverette029942f2014-08-12 14:55:56 -07008795 // Check for known input filter types.
8796 final int numFilters = mFilters.length;
8797 for (int i = 0; i < numFilters; i++) {
8798 final InputFilter filter = mFilters[i];
8799 if (filter instanceof InputFilter.LengthFilter) {
8800 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8801 }
8802 }
8803
Alan Viverette058ac7c2013-08-19 16:44:30 -07008804 if (!isSingleLine()) {
8805 info.setMultiLine(true);
8806 }
Svetoslav7c512842013-01-30 23:02:08 -08008807 }
8808
Alan Viverettecd305ae2014-12-12 14:13:24 -08008809 /**
8810 * Performs an accessibility action after it has been offered to the
8811 * delegate.
8812 *
8813 * @hide
8814 */
Svetoslav7c512842013-01-30 23:02:08 -08008815 @Override
Alan Viverettecd305ae2014-12-12 14:13:24 -08008816 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
Svetoslav7c512842013-01-30 23:02:08 -08008817 switch (action) {
Alan Viveretteb6877882014-09-07 15:49:49 -07008818 case AccessibilityNodeInfo.ACTION_CLICK: {
8819 boolean handled = false;
8820
8821 // Simulate View.onTouchEvent for an ACTION_UP event.
8822 if (isClickable() || isLongClickable()) {
8823 if (isFocusable() && !isFocused()) {
8824 requestFocus();
8825 }
8826
8827 performClick();
8828 handled = true;
8829 }
8830
8831 // Simulate TextView.onTouchEvent for an ACTION_UP event.
8832 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8833 && mText instanceof Spannable && mLayout != null
8834 && (isTextEditable() || isTextSelectable()) && isFocused()) {
8835 // Show the IME, except when selecting in read-only text.
8836 final InputMethodManager imm = InputMethodManager.peekInstance();
8837 viewClicked(imm);
8838 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
8839 handled |= imm.showSoftInput(this, 0);
8840 }
8841 }
8842
8843 return handled;
8844 }
Svetoslav7c512842013-01-30 23:02:08 -08008845 case AccessibilityNodeInfo.ACTION_COPY: {
8846 if (isFocused() && canCopy()) {
8847 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08008848 return true;
8849 }
8850 }
8851 } return false;
8852 case AccessibilityNodeInfo.ACTION_PASTE: {
8853 if (isFocused() && canPaste()) {
8854 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08008855 return true;
8856 }
8857 }
8858 } return false;
8859 case AccessibilityNodeInfo.ACTION_CUT: {
8860 if (isFocused() && canCut()) {
8861 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08008862 return true;
8863 }
8864 }
8865 } return false;
8866 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8867 if (isFocused() && canSelectText()) {
Svet Ganov9e1c67e2014-10-14 08:53:33 -07008868 ensureIterableTextForAccessibilitySelectable();
Svetoslav7c512842013-01-30 23:02:08 -08008869 CharSequence text = getIterableTextForAccessibility();
8870 if (text == null) {
8871 return false;
8872 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008873 final int start = (arguments != null) ? arguments.getInt(
8874 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8875 final int end = (arguments != null) ? arguments.getInt(
8876 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8877 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8878 // No arguments clears the selection.
8879 if (start == end && end == -1) {
8880 Selection.removeSelection((Spannable) text);
Svetoslavd0c83cc2013-02-04 18:39:59 -08008881 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008882 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008883 if (start >= 0 && start <= end && end <= text.length()) {
8884 Selection.setSelection((Spannable) text, start, end);
8885 // Make sure selection mode is engaged.
8886 if (mEditor != null) {
Clara Bayarri29d2b5aa2015-03-13 17:41:56 +00008887 mEditor.startSelectionActionModeWithSelection();
Svetoslavd0c83cc2013-02-04 18:39:59 -08008888 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008889 return true;
8890 }
Svetoslav7c512842013-01-30 23:02:08 -08008891 }
8892 }
8893 } return false;
Svet Ganov9e1c67e2014-10-14 08:53:33 -07008894 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
8895 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
8896 ensureIterableTextForAccessibilitySelectable();
Alan Viverettecd305ae2014-12-12 14:13:24 -08008897 return super.performAccessibilityActionInternal(action, arguments);
Svet Ganov9e1c67e2014-10-14 08:53:33 -07008898 }
Svetoslav7c512842013-01-30 23:02:08 -08008899 default: {
Alan Viverettecd305ae2014-12-12 14:13:24 -08008900 return super.performAccessibilityActionInternal(action, arguments);
Svetoslav7c512842013-01-30 23:02:08 -08008901 }
8902 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008903 }
8904
Alan Viverettea54956a2015-01-07 16:05:02 -08008905 /** @hide */
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008906 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008907 public void sendAccessibilityEventInternal(int eventType) {
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008908 // Do not send scroll events since first they are not interesting for
8909 // accessibility and second such events a generated too frequently.
8910 // For details see the implementation of bringTextIntoView().
8911 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8912 return;
8913 }
Alan Viverettea54956a2015-01-07 16:05:02 -08008914 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008915 }
8916
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008917 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008918 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008919 *
8920 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008921 *
8922 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008923 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008924 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008925 CharSequence text = getText();
8926 if (TextUtils.isEmpty(text)) {
8927 text = getHint();
8928 }
8929 return text;
8930 }
8931
svetoslavganov75986cf2009-05-14 22:28:01 -07008932 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8933 int fromIndex, int removedCount, int addedCount) {
8934 AccessibilityEvent event =
8935 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8936 event.setFromIndex(fromIndex);
8937 event.setRemovedCount(removedCount);
8938 event.setAddedCount(addedCount);
8939 event.setBeforeText(beforeText);
8940 sendAccessibilityEventUnchecked(event);
8941 }
8942
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008943 /**
8944 * Returns whether this text view is a current input method target. The
8945 * default implementation just checks with {@link InputMethodManager}.
8946 */
8947 public boolean isInputMethodTarget() {
8948 InputMethodManager imm = InputMethodManager.peekInstance();
8949 return imm != null && imm.isActive(this);
8950 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008951
Gilles Debunned88876a2012-03-16 17:34:04 -07008952 static final int ID_SELECT_ALL = android.R.id.selectAll;
James Cookf59152c2015-02-26 18:03:58 -08008953 static final int ID_UNDO = android.R.id.undo;
8954 static final int ID_REDO = android.R.id.redo;
Gilles Debunned88876a2012-03-16 17:34:04 -07008955 static final int ID_CUT = android.R.id.cut;
8956 static final int ID_COPY = android.R.id.copy;
8957 static final int ID_PASTE = android.R.id.paste;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09008958 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
Clara Bayarrif84a9722015-03-02 16:09:09 +00008959 static final int ID_REPLACE = android.R.id.replaceText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008960
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008961 /**
8962 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008963 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8964 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008965 *
8966 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008967 */
8968 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008969 int min = 0;
8970 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008971
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008972 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008973 final int selStart = getSelectionStart();
8974 final int selEnd = getSelectionEnd();
8975
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008976 min = Math.max(0, Math.min(selStart, selEnd));
8977 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008978 }
8979
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008980 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008981 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008982 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008983 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008984 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008985 return true;
8986
James Cookf59152c2015-02-26 18:03:58 -08008987 case ID_UNDO:
8988 if (mEditor != null) {
8989 mEditor.undo();
8990 }
8991 return true; // Returns true even if nothing was undone.
8992
8993 case ID_REDO:
8994 if (mEditor != null) {
8995 mEditor.redo();
8996 }
8997 return true; // Returns true even if nothing was undone.
8998
Jeff Brownc1df9072010-12-21 16:38:50 -08008999 case ID_PASTE:
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009000 paste(min, max, true /* withFormatting */);
9001 return true;
9002
9003 case ID_PASTE_AS_PLAIN_TEXT:
9004 paste(min, max, false /* withFormatting */);
Jeff Brownc1df9072010-12-21 16:38:50 -08009005 return true;
9006
9007 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009008 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01009009 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08009010 stopSelectionActionMode();
9011 return true;
9012
9013 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009014 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08009015 stopSelectionActionMode();
9016 return true;
9017 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009018 return false;
9019 }
9020
Gilles Debunned88876a2012-03-16 17:34:04 -07009021 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009022 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9023 }
9024
Gilles Debunnee15b3582010-06-16 15:17:21 -07009025 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009026 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009027 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07009028
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009029 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009030 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009031 }
Gilles Debunnef170a342010-11-11 11:08:59 -08009032
Gilles Debunned88876a2012-03-16 17:34:04 -07009033 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009034 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07009035 }
9036
Gilles Debunne9f102ca2012-02-28 11:15:54 -08009037 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009038 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07009039 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009040 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009041
Gilles Debunne299733e2011-02-07 17:11:41 -08009042 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009043 }
9044
Gilles Debunne60e21862012-01-30 15:04:14 -08009045 @Override
9046 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9047 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08009048 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009049 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08009050 }
9051 }
9052
9053 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009054 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9055 * by the IME or by the spell checker as the user types. This is done by adding
9056 * {@link SuggestionSpan}s to the text.
9057 *
9058 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9059 * user asks for them on these parts of the text. This value depends on the inputType of this
9060 * TextView.
9061 *
9062 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9063 *
9064 * In addition, the type variation must be one of
9065 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9066 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9067 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9068 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9069 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9070 *
9071 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9072 *
9073 * @return true if the suggestions popup window is enabled, based on the inputType.
9074 */
9075 public boolean isSuggestionsEnabled() {
9076 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009077 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9078 return false;
9079 }
9080 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009081
Gilles Debunne2d373a12012-04-20 15:32:19 -07009082 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08009083 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9084 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9085 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9086 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9087 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9088 }
9089
9090 /**
9091 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9092 * selection is initiated in this View.
9093 *
9094 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
9095 * Paste actions, depending on what this View supports.
9096 *
9097 * A custom implementation can add new entries in the default menu in its
9098 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
James Cookd2026682015-03-03 14:40:14 -08009099 * default actions can also be removed from the menu using
9100 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9101 * {@link android.R.id#cut}, {@link android.R.id#copy} or {@link android.R.id#paste} ids as
9102 * parameters.
Gilles Debunne60e21862012-01-30 15:04:14 -08009103 *
9104 * Returning false from
9105 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
9106 * the action mode from being started.
9107 *
9108 * Action click events should be handled by the custom implementation of
9109 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
9110 *
9111 * Note that text selection mode is not started when a TextView receives focus and the
9112 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9113 * that case, to allow for quick replacement.
9114 */
9115 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07009116 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07009117 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009118 }
9119
9120 /**
9121 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9122 *
9123 * @return The current custom selection callback.
9124 */
9125 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009126 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009127 }
9128
9129 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009130 * @hide
9131 */
9132 protected void stopSelectionActionMode() {
James Cookf59152c2015-02-26 18:03:58 -08009133 if (mEditor != null) {
9134 mEditor.stopSelectionActionMode();
9135 }
9136 }
9137
9138 boolean canUndo() {
9139 return mEditor != null && mEditor.canUndo();
9140 }
9141
9142 boolean canRedo() {
9143 return mEditor != null && mEditor.canRedo();
Gilles Debunned88876a2012-03-16 17:34:04 -07009144 }
9145
9146 boolean canCut() {
9147 if (hasPasswordTransformationMethod()) {
9148 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009149 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009150
Gilles Debunne2d373a12012-04-20 15:32:19 -07009151 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9152 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009153 return true;
9154 }
9155
9156 return false;
9157 }
9158
9159 boolean canCopy() {
9160 if (hasPasswordTransformationMethod()) {
9161 return false;
9162 }
9163
Victoria Leased849f542014-01-15 15:15:03 -08009164 if (mText.length() > 0 && hasSelection() && mEditor != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009165 return true;
9166 }
9167
9168 return false;
9169 }
9170
9171 boolean canPaste() {
9172 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07009173 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07009174 getSelectionStart() >= 0 &&
9175 getSelectionEnd() >= 0 &&
9176 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9177 hasPrimaryClip());
9178 }
9179
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009180 boolean canProcessText() {
9181 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9182 || hasPasswordTransformationMethod()) {
9183 return false;
9184 }
9185
9186 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9187 return true;
9188 }
9189
9190 return false;
9191 }
9192
Gilles Debunned88876a2012-03-16 17:34:04 -07009193 boolean selectAllText() {
Seigo Nonakabb6a62c2015-03-31 21:59:30 +09009194 // Need to hide insert point cursor controller before settings selection, otherwise insert
9195 // point cursor controller obtains cursor update event and update cursor with cancelling
9196 // selection.
9197 if (mEditor != null) {
9198 mEditor.hideInsertionPointCursorController();
9199 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009200 final int length = mText.length();
9201 Selection.setSelection((Spannable) mText, 0, length);
9202 return length > 0;
9203 }
9204
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009205 void replaceSelectionWithText(CharSequence text) {
9206 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
Clara Bayarri578286f2015-04-10 15:35:31 +01009207 mEditor.startSelectionActionModeWithSelection();
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009208 }
9209
Gilles Debunned88876a2012-03-16 17:34:04 -07009210 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009211 * Paste clipboard content between min and max positions.
9212 */
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009213 private void paste(int min, int max, boolean withFormatting) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009214 ClipboardManager clipboard =
9215 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9216 ClipData clip = clipboard.getPrimaryClip();
9217 if (clip != null) {
9218 boolean didFirst = false;
9219 for (int i=0; i<clip.getItemCount(); i++) {
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009220 final CharSequence paste;
9221 if (withFormatting) {
9222 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9223 } else {
9224 // Get an item as text and remove all spans by toString().
9225 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9226 paste = (text instanceof Spanned) ? text.toString() : text;
9227 }
Gilles Debunne60e21862012-01-30 15:04:14 -08009228 if (paste != null) {
9229 if (!didFirst) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009230 Selection.setSelection((Spannable) mText, max);
9231 ((Editable) mText).replace(min, max, paste);
9232 didFirst = true;
9233 } else {
9234 ((Editable) mText).insert(getSelectionEnd(), "\n");
9235 ((Editable) mText).insert(getSelectionEnd(), paste);
9236 }
9237 }
9238 }
9239 stopSelectionActionMode();
9240 LAST_CUT_OR_COPY_TIME = 0;
9241 }
9242 }
9243
9244 private void setPrimaryClip(ClipData clip) {
9245 ClipboardManager clipboard = (ClipboardManager) getContext().
9246 getSystemService(Context.CLIPBOARD_SERVICE);
9247 clipboard.setPrimaryClip(clip);
9248 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
9249 }
9250
Gilles Debunne60e21862012-01-30 15:04:14 -08009251 /**
9252 * Get the character offset closest to the specified absolute position. A typical use case is to
9253 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9254 *
9255 * @param x The horizontal absolute position of a point on screen
9256 * @param y The vertical absolute position of a point on screen
9257 * @return the character offset for the character whose position is closest to the specified
9258 * position. Returns -1 if there is no layout.
9259 */
9260 public int getOffsetForPosition(float x, float y) {
9261 if (getLayout() == null) return -1;
9262 final int line = getLineAtCoordinate(y);
9263 final int offset = getOffsetAtCoordinate(line, x);
9264 return offset;
9265 }
9266
Gilles Debunned88876a2012-03-16 17:34:04 -07009267 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009268 x -= getTotalPaddingLeft();
9269 // Clamp the position to inside of the view.
9270 x = Math.max(0.0f, x);
9271 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9272 x += getScrollX();
9273 return x;
9274 }
9275
Gilles Debunned88876a2012-03-16 17:34:04 -07009276 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009277 y -= getTotalPaddingTop();
9278 // Clamp the position to inside of the view.
9279 y = Math.max(0.0f, y);
9280 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9281 y += getScrollY();
9282 return getLayout().getLineForVertical((int) y);
9283 }
9284
Mady Mellor2ff2cd82015-03-02 10:37:01 -08009285 int getOffsetAtCoordinate(int line, float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009286 x = convertToLocalHorizontalCoordinate(x);
9287 return getLayout().getOffsetForHorizontal(line, x);
9288 }
9289
Gilles Debunne60e21862012-01-30 15:04:14 -08009290 @Override
9291 public boolean onDragEvent(DragEvent event) {
9292 switch (event.getAction()) {
9293 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009294 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08009295
9296 case DragEvent.ACTION_DRAG_ENTERED:
9297 TextView.this.requestFocus();
9298 return true;
9299
9300 case DragEvent.ACTION_DRAG_LOCATION:
9301 final int offset = getOffsetForPosition(event.getX(), event.getY());
9302 Selection.setSelection((Spannable)mText, offset);
9303 return true;
9304
9305 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009306 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08009307 return true;
9308
9309 case DragEvent.ACTION_DRAG_ENDED:
9310 case DragEvent.ACTION_DRAG_EXITED:
9311 default:
9312 return true;
9313 }
9314 }
9315
Gilles Debunne60e21862012-01-30 15:04:14 -08009316 boolean isInBatchEditMode() {
9317 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009318 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08009319 if (ims != null) {
9320 return ims.mBatchEditNesting > 0;
9321 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07009322 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08009323 }
9324
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009325 @Override
9326 public void onRtlPropertiesChanged(int layoutDirection) {
9327 super.onRtlPropertiesChanged(layoutDirection);
9328
9329 mTextDir = getTextDirectionHeuristic();
Fabrice Di Meglio22228fe2014-01-06 16:30:43 -08009330
9331 if (mLayout != null) {
9332 checkForRelayout();
9333 }
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009334 }
9335
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009336 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08009337 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08009338 // passwords fields should be LTR
9339 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009340 }
9341
9342 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07009343 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08009344
9345 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07009346 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009347 default:
9348 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009349 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08009350 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08009351 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009352 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009353 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009354 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009355 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009356 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009357 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009358 return TextDirectionHeuristics.LOCALE;
Roozbeh Pournaderb51222a2015-04-13 14:33:40 -07009359 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9360 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9361 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9362 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009363 }
9364 }
9365
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009366 /**
9367 * @hide
9368 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009369 @Override
9370 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009371 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009372 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009373 return;
9374 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009375 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08009376
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009377 // Resolve drawables
9378 if (mDrawables != null) {
9379 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009380 }
9381 }
9382
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07009383 /**
9384 * @hide
9385 */
Gilles Debunne60e21862012-01-30 15:04:14 -08009386 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009387 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009388 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08009389 }
9390
9391 /**
9392 * @hide
9393 */
9394 protected void viewClicked(InputMethodManager imm) {
9395 if (imm != null) {
9396 imm.viewClicked(this);
9397 }
9398 }
9399
9400 /**
9401 * Deletes the range of text [start, end[.
9402 * @hide
9403 */
9404 protected void deleteText_internal(int start, int end) {
9405 ((Editable) mText).delete(start, end);
9406 }
9407
9408 /**
9409 * Replaces the range of text [start, end[ by replacement text
9410 * @hide
9411 */
9412 protected void replaceText_internal(int start, int end, CharSequence text) {
9413 ((Editable) mText).replace(start, end, text);
9414 }
9415
9416 /**
9417 * Sets a span on the specified range of text
9418 * @hide
9419 */
9420 protected void setSpan_internal(Object span, int start, int end, int flags) {
9421 ((Editable) mText).setSpan(span, start, end, flags);
9422 }
9423
9424 /**
9425 * Moves the cursor to the specified offset position in text
9426 * @hide
9427 */
9428 protected void setCursorPosition_internal(int start, int end) {
9429 Selection.setSelection(((Editable) mText), start, end);
9430 }
9431
9432 /**
9433 * An Editor should be created as soon as any of the editable-specific fields (grouped
9434 * inside the Editor object) is assigned to a non-default value.
9435 * This method will create the Editor if needed.
9436 *
9437 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9438 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9439 * Editor for backward compatibility, as soon as one of these fields is assigned.
9440 *
9441 * Also note that for performance reasons, the mEditor is created when needed, but not
9442 * reset when no more edit-specific fields are needed.
9443 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07009444 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08009445 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009446 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08009447 }
9448 }
9449
Gilles Debunne60e21862012-01-30 15:04:14 -08009450 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009451 * @hide
9452 */
9453 @Override
9454 public CharSequence getIterableTextForAccessibility() {
Svet Ganov9e1c67e2014-10-14 08:53:33 -07009455 return mText;
9456 }
9457
9458 private void ensureIterableTextForAccessibilitySelectable() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07009459 if (!(mText instanceof Spannable)) {
9460 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009461 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009462 }
9463
9464 /**
9465 * @hide
9466 */
9467 @Override
9468 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9469 switch (granularity) {
9470 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9471 Spannable text = (Spannable) getIterableTextForAccessibility();
9472 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9473 AccessibilityIterators.LineTextSegmentIterator iterator =
9474 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9475 iterator.initialize(text, getLayout());
9476 return iterator;
9477 }
9478 } break;
9479 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9480 Spannable text = (Spannable) getIterableTextForAccessibility();
9481 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9482 AccessibilityIterators.PageTextSegmentIterator iterator =
9483 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9484 iterator.initialize(this);
9485 return iterator;
9486 }
9487 } break;
9488 }
9489 return super.getIteratorForGranularity(granularity);
9490 }
9491
9492 /**
9493 * @hide
9494 */
9495 @Override
Svetoslav7c512842013-01-30 23:02:08 -08009496 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07009497 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -08009498 }
9499
9500 /**
9501 * @hide
9502 */
9503 public boolean isAccessibilitySelectionExtendable() {
9504 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009505 }
9506
9507 /**
9508 * @hide
9509 */
9510 @Override
Svetoslav7c512842013-01-30 23:02:08 -08009511 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07009512 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -08009513 }
9514
9515 /**
9516 * @hide
9517 */
9518 @Override
9519 public void setAccessibilitySelection(int start, int end) {
9520 if (getAccessibilitySelectionStart() == start
9521 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009522 return;
9523 }
Svetoslavabad55d2013-05-07 18:49:51 -07009524 // Hide all selection controllers used for adjusting selection
9525 // since we are doing so explicitlty by other means and these
9526 // controllers interact with how selection behaves.
9527 if (mEditor != null) {
9528 mEditor.hideControllers();
9529 }
Svetoslav7c512842013-01-30 23:02:08 -08009530 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -07009531 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -08009532 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009533 } else {
Svetoslav7c512842013-01-30 23:02:08 -08009534 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009535 }
9536 }
9537
9538 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009539 * User interface state that is stored by TextView for implementing
9540 * {@link View#onSaveInstanceState}.
9541 */
9542 public static class SavedState extends BaseSavedState {
9543 int selStart;
9544 int selEnd;
9545 CharSequence text;
9546 boolean frozenWithFocus;
9547 CharSequence error;
James Cookf59152c2015-02-26 18:03:58 -08009548 ParcelableParcel editorState; // Optional state from Editor.
Gilles Debunne60e21862012-01-30 15:04:14 -08009549
9550 SavedState(Parcelable superState) {
9551 super(superState);
9552 }
9553
9554 @Override
9555 public void writeToParcel(Parcel out, int flags) {
9556 super.writeToParcel(out, flags);
9557 out.writeInt(selStart);
9558 out.writeInt(selEnd);
9559 out.writeInt(frozenWithFocus ? 1 : 0);
9560 TextUtils.writeToParcel(text, out, flags);
9561
9562 if (error == null) {
9563 out.writeInt(0);
9564 } else {
9565 out.writeInt(1);
9566 TextUtils.writeToParcel(error, out, flags);
9567 }
James Cookf59152c2015-02-26 18:03:58 -08009568
9569 if (editorState == null) {
9570 out.writeInt(0);
9571 } else {
9572 out.writeInt(1);
9573 editorState.writeToParcel(out, flags);
9574 }
Gilles Debunne60e21862012-01-30 15:04:14 -08009575 }
9576
9577 @Override
9578 public String toString() {
9579 String str = "TextView.SavedState{"
9580 + Integer.toHexString(System.identityHashCode(this))
9581 + " start=" + selStart + " end=" + selEnd;
9582 if (text != null) {
9583 str += " text=" + text;
9584 }
9585 return str + "}";
9586 }
9587
9588 @SuppressWarnings("hiding")
9589 public static final Parcelable.Creator<SavedState> CREATOR
9590 = new Parcelable.Creator<SavedState>() {
9591 public SavedState createFromParcel(Parcel in) {
9592 return new SavedState(in);
9593 }
9594
9595 public SavedState[] newArray(int size) {
9596 return new SavedState[size];
9597 }
9598 };
9599
9600 private SavedState(Parcel in) {
9601 super(in);
9602 selStart = in.readInt();
9603 selEnd = in.readInt();
9604 frozenWithFocus = (in.readInt() != 0);
9605 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9606
9607 if (in.readInt() != 0) {
9608 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9609 }
James Cookf59152c2015-02-26 18:03:58 -08009610
9611 if (in.readInt() != 0) {
9612 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
9613 }
Gilles Debunne60e21862012-01-30 15:04:14 -08009614 }
9615 }
9616
9617 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9618 private char[] mChars;
9619 private int mStart, mLength;
9620
9621 public CharWrapper(char[] chars, int start, int len) {
9622 mChars = chars;
9623 mStart = start;
9624 mLength = len;
9625 }
9626
9627 /* package */ void set(char[] chars, int start, int len) {
9628 mChars = chars;
9629 mStart = start;
9630 mLength = len;
9631 }
9632
9633 public int length() {
9634 return mLength;
9635 }
9636
9637 public char charAt(int off) {
9638 return mChars[off + mStart];
9639 }
9640
9641 @Override
9642 public String toString() {
9643 return new String(mChars, mStart, mLength);
9644 }
9645
9646 public CharSequence subSequence(int start, int end) {
9647 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9648 throw new IndexOutOfBoundsException(start + ", " + end);
9649 }
9650
9651 return new String(mChars, start + mStart, end - start);
9652 }
9653
9654 public void getChars(int start, int end, char[] buf, int off) {
9655 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9656 throw new IndexOutOfBoundsException(start + ", " + end);
9657 }
9658
9659 System.arraycopy(mChars, start + mStart, buf, off, end - start);
9660 }
9661
9662 public void drawText(Canvas c, int start, int end,
9663 float x, float y, Paint p) {
9664 c.drawText(mChars, start + mStart, end - start, x, y, p);
9665 }
9666
9667 public void drawTextRun(Canvas c, int start, int end,
Raph Levien051910b2014-06-15 18:25:29 -07009668 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009669 int count = end - start;
9670 int contextCount = contextEnd - contextStart;
9671 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -07009672 contextCount, x, y, isRtl, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08009673 }
9674
9675 public float measureText(int start, int end, Paint p) {
9676 return p.measureText(mChars, start + mStart, end - start);
9677 }
9678
9679 public int getTextWidths(int start, int end, float[] widths, Paint p) {
9680 return p.getTextWidths(mChars, start + mStart, end - start, widths);
9681 }
9682
9683 public float getTextRunAdvances(int start, int end, int contextStart,
Raph Levien051910b2014-06-15 18:25:29 -07009684 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08009685 Paint p) {
9686 int count = end - start;
9687 int contextCount = contextEnd - contextStart;
9688 return p.getTextRunAdvances(mChars, start + mStart, count,
Raph Levien051910b2014-06-15 18:25:29 -07009689 contextStart + mStart, contextCount, isRtl, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08009690 advancesIndex);
9691 }
9692
Raph Levien051910b2014-06-15 18:25:29 -07009693 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
Gilles Debunne60e21862012-01-30 15:04:14 -08009694 int offset, int cursorOpt, Paint p) {
9695 int contextCount = contextEnd - contextStart;
9696 return p.getTextRunCursor(mChars, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -07009697 contextCount, dir, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08009698 }
9699 }
9700
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009701 private static final class Marquee {
Gilles Debunne60e21862012-01-30 15:04:14 -08009702 // TODO: Add an option to configure this
9703 private static final float MARQUEE_DELTA_MAX = 0.07f;
9704 private static final int MARQUEE_DELAY = 1200;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009705 private static final int MARQUEE_DP_PER_SECOND = 30;
Gilles Debunne60e21862012-01-30 15:04:14 -08009706
9707 private static final byte MARQUEE_STOPPED = 0x0;
9708 private static final byte MARQUEE_STARTING = 0x1;
9709 private static final byte MARQUEE_RUNNING = 0x2;
9710
Gilles Debunne60e21862012-01-30 15:04:14 -08009711 private final WeakReference<TextView> mView;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009712 private final Choreographer mChoreographer;
Gilles Debunne60e21862012-01-30 15:04:14 -08009713
9714 private byte mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009715 private final float mPixelsPerSecond;
Gilles Debunne60e21862012-01-30 15:04:14 -08009716 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009717 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009718 private float mGhostStart;
9719 private float mGhostOffset;
9720 private float mFadeStop;
9721 private int mRepeatLimit;
9722
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009723 private float mScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009724 private long mLastAnimationMs;
Gilles Debunne60e21862012-01-30 15:04:14 -08009725
9726 Marquee(TextView v) {
9727 final float density = v.getContext().getResources().getDisplayMetrics().density;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009728 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
Gilles Debunne60e21862012-01-30 15:04:14 -08009729 mView = new WeakReference<TextView>(v);
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009730 mChoreographer = Choreographer.getInstance();
Gilles Debunne60e21862012-01-30 15:04:14 -08009731 }
9732
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009733 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
9734 @Override
9735 public void doFrame(long frameTimeNanos) {
9736 tick();
Gilles Debunne60e21862012-01-30 15:04:14 -08009737 }
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009738 };
9739
9740 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
9741 @Override
9742 public void doFrame(long frameTimeNanos) {
9743 mStatus = MARQUEE_RUNNING;
9744 mLastAnimationMs = mChoreographer.getFrameTime();
9745 tick();
9746 }
9747 };
9748
9749 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
9750 @Override
9751 public void doFrame(long frameTimeNanos) {
9752 if (mStatus == MARQUEE_RUNNING) {
9753 if (mRepeatLimit >= 0) {
9754 mRepeatLimit--;
9755 }
9756 start(mRepeatLimit);
9757 }
9758 }
9759 };
Gilles Debunne60e21862012-01-30 15:04:14 -08009760
9761 void tick() {
9762 if (mStatus != MARQUEE_RUNNING) {
9763 return;
9764 }
9765
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009766 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -08009767
9768 final TextView textView = mView.get();
9769 if (textView != null && (textView.isFocused() || textView.isSelected())) {
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009770 long currentMs = mChoreographer.getFrameTime();
9771 long deltaMs = currentMs - mLastAnimationMs;
9772 mLastAnimationMs = currentMs;
9773 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
9774 mScroll += deltaPx;
Gilles Debunne60e21862012-01-30 15:04:14 -08009775 if (mScroll > mMaxScroll) {
9776 mScroll = mMaxScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009777 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
Gilles Debunne60e21862012-01-30 15:04:14 -08009778 } else {
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009779 mChoreographer.postFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -08009780 }
9781 textView.invalidate();
9782 }
9783 }
9784
9785 void stop() {
9786 mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009787 mChoreographer.removeFrameCallback(mStartCallback);
9788 mChoreographer.removeFrameCallback(mRestartCallback);
9789 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -08009790 resetScroll();
9791 }
9792
9793 private void resetScroll() {
9794 mScroll = 0.0f;
9795 final TextView textView = mView.get();
9796 if (textView != null) textView.invalidate();
9797 }
9798
9799 void start(int repeatLimit) {
9800 if (repeatLimit == 0) {
9801 stop();
9802 return;
9803 }
9804 mRepeatLimit = repeatLimit;
9805 final TextView textView = mView.get();
9806 if (textView != null && textView.mLayout != null) {
9807 mStatus = MARQUEE_STARTING;
9808 mScroll = 0.0f;
9809 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9810 textView.getCompoundPaddingRight();
9811 final float lineWidth = textView.mLayout.getLineWidth(0);
9812 final float gap = textWidth / 3.0f;
9813 mGhostStart = lineWidth - textWidth + gap;
9814 mMaxScroll = mGhostStart + textWidth;
9815 mGhostOffset = lineWidth + gap;
9816 mFadeStop = lineWidth + textWidth / 6.0f;
9817 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9818
9819 textView.invalidate();
Jorim Jaggi3aa42202014-07-01 14:59:34 +02009820 mChoreographer.postFrameCallback(mStartCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -08009821 }
9822 }
9823
9824 float getGhostOffset() {
9825 return mGhostOffset;
9826 }
9827
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009828 float getScroll() {
9829 return mScroll;
9830 }
9831
9832 float getMaxFadeScroll() {
9833 return mMaxFadeScroll;
9834 }
9835
Gilles Debunne60e21862012-01-30 15:04:14 -08009836 boolean shouldDrawLeftFade() {
9837 return mScroll <= mFadeStop;
9838 }
9839
9840 boolean shouldDrawGhost() {
9841 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9842 }
9843
9844 boolean isRunning() {
9845 return mStatus == MARQUEE_RUNNING;
9846 }
9847
9848 boolean isStopped() {
9849 return mStatus == MARQUEE_STOPPED;
9850 }
9851 }
9852
Gilles Debunne60e21862012-01-30 15:04:14 -08009853 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9854
9855 private CharSequence mBeforeText;
9856
Gilles Debunne60e21862012-01-30 15:04:14 -08009857 public void beforeTextChanged(CharSequence buffer, int start,
9858 int before, int after) {
9859 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9860 + " before=" + before + " after=" + after + ": " + buffer);
9861
9862 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08009863 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9864 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009865 mBeforeText = buffer.toString();
9866 }
9867
9868 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9869 }
9870
Gilles Debunned88876a2012-03-16 17:34:04 -07009871 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009872 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9873 + " before=" + before + " after=" + after + ": " + buffer);
9874 TextView.this.handleTextChanged(buffer, start, before, after);
9875
Gilles Debunne60e21862012-01-30 15:04:14 -08009876 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9877 (isFocused() || isSelected() && isShown())) {
9878 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9879 mBeforeText = null;
9880 }
9881 }
9882
9883 public void afterTextChanged(Editable buffer) {
9884 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9885 TextView.this.sendAfterTextChanged(buffer);
9886
9887 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9888 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9889 }
9890 }
9891
Gilles Debunned88876a2012-03-16 17:34:04 -07009892 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009893 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9894 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9895 TextView.this.spanChange(buf, what, s, st, e, en);
9896 }
9897
9898 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9899 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9900 + " what=" + what + ": " + buf);
9901 TextView.this.spanChange(buf, what, -1, s, -1, e);
9902 }
9903
9904 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9905 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9906 + " what=" + what + ": " + buf);
9907 TextView.this.spanChange(buf, what, s, -1, e, -1);
9908 }
satoka67a3cf2011-09-07 17:14:03 +09009909 }
Mady Mellor138bc2f2015-03-05 15:15:25 +00009910}