blob: f19bf02a238f746061c8bfc5538d1777555fd60b [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
Andrei Stingaceanu2aaeefe2015-10-20 19:11:23 +010019import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
Gilles Debunne78996c92010-10-12 16:01:47 -070020import android.R;
Tor Norbye80756e32015-03-02 09:39:27 -080021import android.annotation.ColorInt;
Tor Norbye7b9c9122013-05-30 16:48:33 -070022import android.annotation.DrawableRes;
Alan Viverette97f84ee2014-09-09 16:55:56 -070023import android.annotation.NonNull;
Raph Leviene272a262014-08-07 16:07:51 -070024import android.annotation.Nullable;
Roozbeh Pournadera23748a2015-08-31 14:30:36 -070025import android.annotation.Size;
Tor Norbye7b9c9122013-05-30 16:48:33 -070026import android.annotation.StringRes;
27import android.annotation.StyleRes;
28import android.annotation.XmlRes;
Clara Bayarri5b7665a2015-05-15 16:32:50 +010029import android.app.Activity;
Dianne Hackborn16036f22015-06-22 14:05:51 -070030import android.app.assist.AssistStructure;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070031import android.content.ClipData;
Gilles Debunne64e54a62010-09-07 19:07:17 -070032import android.content.ClipboardManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.content.Context;
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +000034import android.content.Intent;
Dianne Hackborn3aa49b62013-04-26 16:39:17 -070035import android.content.UndoManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.content.res.ColorStateList;
Christopher Tate1373a8e2011-11-10 19:59:13 -080037import android.content.res.CompatibilityInfo;
Raph Levien2e3aa442015-01-14 16:12:53 -080038import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.content.res.Resources;
40import android.content.res.TypedArray;
41import android.content.res.XmlResourceParser;
42import android.graphics.Canvas;
Philip Milne7b757812012-09-19 18:13:44 -070043import android.graphics.Insets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.graphics.Paint;
45import android.graphics.Path;
Alan Viveretteb97d6982015-01-07 16:16:20 -080046import android.graphics.PorterDuff;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047import android.graphics.Rect;
48import android.graphics.RectF;
49import android.graphics.Typeface;
50import android.graphics.drawable.Drawable;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090051import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.os.Parcel;
54import android.os.Parcelable;
James Cookf59152c2015-02-26 18:03:58 -080055import android.os.ParcelableParcel;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056import android.os.SystemClock;
Alan Viveretteb6e0cb92014-11-24 15:13:43 -080057import android.os.UserHandle;
alanv7d624192012-05-21 14:23:17 -070058import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.text.BoringLayout;
60import android.text.DynamicLayout;
61import android.text.Editable;
62import android.text.GetChars;
63import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070065import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066import android.text.Layout;
67import android.text.ParcelableSpan;
68import android.text.Selection;
69import android.text.SpanWatcher;
70import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070071import android.text.SpannableString;
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -070072import android.text.SpannableStringBuilder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073import android.text.Spanned;
74import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070076import android.text.TextDirectionHeuristic;
77import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078import android.text.TextPaint;
79import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070080import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070081import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070082import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080083import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084import android.text.method.DateKeyListener;
85import android.text.method.DateTimeKeyListener;
86import android.text.method.DialerKeyListener;
87import android.text.method.DigitsKeyListener;
88import android.text.method.KeyListener;
89import android.text.method.LinkMovementMethod;
90import android.text.method.MetaKeyKeyListener;
91import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092import android.text.method.PasswordTransformationMethod;
93import android.text.method.SingleLineTransformationMethod;
94import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070095import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070097import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070098import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080099import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -0800100import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -0700102import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -0700103import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104import android.text.style.URLSpan;
105import android.text.style.UpdateAppearance;
106import android.text.util.Linkify;
107import android.util.AttributeSet;
Roozbeh Pournadera23748a2015-08-31 14:30:36 -0700108import android.util.LocaleList;
svetoslavganov75986cf2009-05-14 22:28:01 -0700109import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -0700111import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -0700112import android.view.ActionMode;
Jorim Jaggi3aa42202014-07-01 14:59:34 +0200113import android.view.Choreographer;
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +0900114import android.view.ContextMenu;
Gilles Debunnef170a342010-11-11 11:08:59 -0800115import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700117import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800118import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120import android.view.MotionEvent;
Jun Mukai1db53972015-09-11 18:08:31 -0700121import android.view.PointerIcon;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700123import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700125import android.view.ViewGroup.LayoutParams;
Siva Velusamy94a6d152015-05-05 15:07:00 -0700126import android.view.ViewHierarchyEncoder;
Andrei Stingaceanu2aaeefe2015-10-20 19:11:23 +0100127import android.view.ViewParent;
128import android.view.ViewRootImpl;
129import android.view.ViewStructure;
130import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700131import android.view.accessibility.AccessibilityEvent;
132import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700133import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134import android.view.animation.AnimationUtils;
135import android.view.inputmethod.BaseInputConnection;
136import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800137import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700138import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139import android.view.inputmethod.ExtractedText;
140import android.view.inputmethod.ExtractedTextRequest;
141import android.view.inputmethod.InputConnection;
142import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900143import android.view.textservice.SpellCheckerSubtype;
144import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145import android.widget.RemoteViews.RemoteView;
146
Seigo Nonakaa60160b2015-08-19 12:38:35 -0700147import com.android.internal.annotations.VisibleForTesting;
Gilles Debunne22378292011-08-12 10:38:52 -0700148import com.android.internal.util.FastMath;
149import com.android.internal.widget.EditableInputConnection;
150
151import org.xmlpull.v1.XmlPullParserException;
152
Gilles Debunne27113f82010-08-23 12:09:14 -0700153import java.io.IOException;
154import java.lang.ref.WeakReference;
155import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700156import java.util.Locale;
Gilles Debunne27113f82010-08-23 12:09:14 -0700157
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158/**
159 * Displays text to the user and optionally allows them to edit it. A TextView
160 * is a complete text editor, however the basic class is configured to not
161 * allow editing; see {@link EditText} for a subclass that configures the text
162 * view for editing.
163 *
164 * <p>
Joe Malin10d96952013-05-29 17:49:09 -0700165 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
166 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
167 * android:textIsSelectable} to "true" or call
168 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
169 * allows users to make selection gestures in the TextView, which in turn triggers the system's
170 * built-in copy/paste controls.
171 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 * <b>XML attributes</b>
173 * <p>
174 * See {@link android.R.styleable#TextView TextView Attributes},
175 * {@link android.R.styleable#View View Attributes}
176 *
177 * @attr ref android.R.styleable#TextView_text
178 * @attr ref android.R.styleable#TextView_bufferType
179 * @attr ref android.R.styleable#TextView_hint
180 * @attr ref android.R.styleable#TextView_textColor
181 * @attr ref android.R.styleable#TextView_textColorHighlight
182 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700183 * @attr ref android.R.styleable#TextView_textAppearance
184 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 * @attr ref android.R.styleable#TextView_textSize
186 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700187 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 * @attr ref android.R.styleable#TextView_typeface
189 * @attr ref android.R.styleable#TextView_textStyle
190 * @attr ref android.R.styleable#TextView_cursorVisible
191 * @attr ref android.R.styleable#TextView_maxLines
192 * @attr ref android.R.styleable#TextView_maxHeight
193 * @attr ref android.R.styleable#TextView_lines
194 * @attr ref android.R.styleable#TextView_height
195 * @attr ref android.R.styleable#TextView_minLines
196 * @attr ref android.R.styleable#TextView_minHeight
197 * @attr ref android.R.styleable#TextView_maxEms
198 * @attr ref android.R.styleable#TextView_maxWidth
199 * @attr ref android.R.styleable#TextView_ems
200 * @attr ref android.R.styleable#TextView_width
201 * @attr ref android.R.styleable#TextView_minEms
202 * @attr ref android.R.styleable#TextView_minWidth
203 * @attr ref android.R.styleable#TextView_gravity
204 * @attr ref android.R.styleable#TextView_scrollHorizontally
205 * @attr ref android.R.styleable#TextView_password
206 * @attr ref android.R.styleable#TextView_singleLine
207 * @attr ref android.R.styleable#TextView_selectAllOnFocus
208 * @attr ref android.R.styleable#TextView_includeFontPadding
209 * @attr ref android.R.styleable#TextView_maxLength
210 * @attr ref android.R.styleable#TextView_shadowColor
211 * @attr ref android.R.styleable#TextView_shadowDx
212 * @attr ref android.R.styleable#TextView_shadowDy
213 * @attr ref android.R.styleable#TextView_shadowRadius
214 * @attr ref android.R.styleable#TextView_autoLink
215 * @attr ref android.R.styleable#TextView_linksClickable
216 * @attr ref android.R.styleable#TextView_numeric
217 * @attr ref android.R.styleable#TextView_digits
218 * @attr ref android.R.styleable#TextView_phoneNumber
219 * @attr ref android.R.styleable#TextView_inputMethod
220 * @attr ref android.R.styleable#TextView_capitalize
221 * @attr ref android.R.styleable#TextView_autoText
222 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700223 * @attr ref android.R.styleable#TextView_freezesText
224 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 * @attr ref android.R.styleable#TextView_drawableTop
226 * @attr ref android.R.styleable#TextView_drawableBottom
227 * @attr ref android.R.styleable#TextView_drawableRight
228 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800229 * @attr ref android.R.styleable#TextView_drawableStart
230 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700231 * @attr ref android.R.styleable#TextView_drawablePadding
Alan Viveretteb97d6982015-01-07 16:16:20 -0800232 * @attr ref android.R.styleable#TextView_drawableTint
233 * @attr ref android.R.styleable#TextView_drawableTintMode
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 * @attr ref android.R.styleable#TextView_lineSpacingExtra
235 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
236 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700237 * @attr ref android.R.styleable#TextView_inputType
238 * @attr ref android.R.styleable#TextView_imeOptions
239 * @attr ref android.R.styleable#TextView_privateImeOptions
240 * @attr ref android.R.styleable#TextView_imeActionLabel
241 * @attr ref android.R.styleable#TextView_imeActionId
242 * @attr ref android.R.styleable#TextView_editorExtras
Raph Leviene1c4a0d2014-06-05 15:26:59 -0700243 * @attr ref android.R.styleable#TextView_elegantTextHeight
Raph Leviene272a262014-08-07 16:07:51 -0700244 * @attr ref android.R.styleable#TextView_letterSpacing
245 * @attr ref android.R.styleable#TextView_fontFeatureSettings
Raph Leviene319d5a2015-04-14 23:51:07 -0700246 * @attr ref android.R.styleable#TextView_breakStrategy
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700247 * @attr ref android.R.styleable#TextView_hyphenationFrequency
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 */
249@RemoteView
250public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700251 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 // Enum for the "typeface" XML parameter.
255 // TODO: How can we get this from the XML instead of hardcoding it here?
256 private static final int SANS = 1;
257 private static final int SERIF = 2;
258 private static final int MONOSPACE = 3;
259
260 // Bitfield for the "numeric" XML parameter.
261 // TODO: How can we get this from the XML instead of hardcoding it here?
262 private static final int SIGNED = 2;
263 private static final int DECIMAL = 4;
264
Adam Powell282e3772011-08-30 16:51:11 -0700265 /**
266 * Draw marquee text with fading edges as usual
267 */
268 private static final int MARQUEE_FADE_NORMAL = 0;
269
270 /**
271 * Draw marquee text as ellipsize end while inactive instead of with the fade.
272 * (Useful for devices where the fade can be expensive if overdone)
273 */
274 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
275
276 /**
277 * Draw marquee text with fading edges because it is currently active/animating.
278 */
279 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
280
Gilles Debunne60e21862012-01-30 15:04:14 -0800281 private static final int LINES = 1;
282 private static final int EMS = LINES;
283 private static final int PIXELS = 2;
284
285 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800286
287 // XXX should be much larger
288 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800289 private static final int ANIMATED_SCROLL_GAP = 250;
290
291 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
292 private static final Spanned EMPTY_SPANNED = new SpannedString("");
293
Gilles Debunne60e21862012-01-30 15:04:14 -0800294 private static final int CHANGE_WATCHER_PRIORITY = 100;
295
296 // New state used to change background based on whether this TextView is multiline.
297 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
298
Abodunrinwa Toki0e5c6032015-06-26 14:11:21 -0700299 // Accessibility action to share selected text.
300 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
301
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -0700302 /**
303 * @hide
304 */
305 // Accessibility action start id for "process text" actions.
306 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
Gilles Debunne60e21862012-01-30 15:04:14 -0800307
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +0000308 /**
309 * @hide
310 */
311 static final int PROCESS_TEXT_REQUEST_CODE = 100;
312
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +0900313 /**
314 * Return code of {@link #doKeyDown}.
315 */
316 private static final int KEY_EVENT_NOT_HANDLED = 0;
317 private static final int KEY_EVENT_HANDLED = -1;
318 private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1;
319 private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2;
320
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -0700321 // System wide time for last cut, copy or text changed action.
322 static long sLastCutCopyOrTextChangedTime;
323
Gilles Debunne60e21862012-01-30 15:04:14 -0800324 private ColorStateList mTextColor;
325 private ColorStateList mHintTextColor;
326 private ColorStateList mLinkTextColor;
Jon Mirandaa25dc4282014-07-01 14:35:07 -0700327 @ViewDebug.ExportedProperty(category = "text")
Gilles Debunne60e21862012-01-30 15:04:14 -0800328 private int mCurTextColor;
329 private int mCurHintTextColor;
330 private boolean mFreezesText;
Gilles Debunne60e21862012-01-30 15:04:14 -0800331 private boolean mDispatchTemporaryDetach;
332
Alan Viverettedf689992014-11-13 12:59:37 -0800333 /** Whether this view is temporarily detached from the parent view. */
334 boolean mTemporaryDetach;
335
Gilles Debunne60e21862012-01-30 15:04:14 -0800336 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
337 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
338
339 private float mShadowRadius, mShadowDx, mShadowDy;
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -0400340 private int mShadowColor;
341
Gilles Debunne60e21862012-01-30 15:04:14 -0800342 private boolean mPreDrawRegistered;
Craig Stoutf209ef92014-07-02 15:23:16 -0700343 private boolean mPreDrawListenerDetached;
Gilles Debunne60e21862012-01-30 15:04:14 -0800344
Michael Wright3a7e4832013-02-11 15:55:50 -0800345 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
346 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
347 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
348 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
349 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
350 private boolean mPreventDefaultMovement;
351
Gilles Debunne60e21862012-01-30 15:04:14 -0800352 private TextUtils.TruncateAt mEllipsize;
353
354 static class Drawables {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800355 static final int LEFT = 0;
356 static final int TOP = 1;
357 static final int RIGHT = 2;
358 static final int BOTTOM = 3;
359
360 static final int DRAWABLE_NONE = -1;
361 static final int DRAWABLE_RIGHT = 0;
362 static final int DRAWABLE_LEFT = 1;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800363
Gilles Debunne60e21862012-01-30 15:04:14 -0800364 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800365
Alan Viveretteb97d6982015-01-07 16:16:20 -0800366 final Drawable[] mShowing = new Drawable[4];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800367
Alan Viveretteb97d6982015-01-07 16:16:20 -0800368 ColorStateList mTintList;
369 PorterDuff.Mode mTintMode;
370 boolean mHasTint;
371 boolean mHasTintMode;
372
373 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700374 Drawable mDrawableLeftInitial, mDrawableRightInitial;
Alan Viveretteb97d6982015-01-07 16:16:20 -0800375
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700376 boolean mIsRtlCompatibilityMode;
377 boolean mOverride;
378
Gilles Debunne60e21862012-01-30 15:04:14 -0800379 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800380 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
381
Gilles Debunne60e21862012-01-30 15:04:14 -0800382 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800383 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
384
Gilles Debunne60e21862012-01-30 15:04:14 -0800385 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800386
387 int mDrawableSaved = DRAWABLE_NONE;
388
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700389 public Drawables(Context context) {
390 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
391 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
392 !context.getApplicationInfo().hasRtlSupport());
393 mOverride = false;
394 }
395
Alan Viverette189d4f52015-12-16 16:06:23 -0500396 /**
Alan Viverette1c9d20b2016-02-03 10:45:55 -0500397 * @return {@code true} if this object contains metadata that needs to
398 * be retained, {@code false} otherwise
399 */
400 public boolean hasMetadata() {
401 return mDrawablePadding != 0 || mHasTintMode || mHasTint;
402 }
403
404 /**
Alan Viverette189d4f52015-12-16 16:06:23 -0500405 * Updates the list of displayed drawables to account for the current
406 * layout direction.
407 *
408 * @param layoutDirection the current layout direction
409 * @return {@code true} if the displayed drawables changed
410 */
411 public boolean resolveWithLayoutDirection(int layoutDirection) {
412 final Drawable previousLeft = mShowing[Drawables.LEFT];
413 final Drawable previousRight = mShowing[Drawables.RIGHT];
414
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700415 // First reset "left" and "right" drawables to their initial values
Alan Viveretteb97d6982015-01-07 16:16:20 -0800416 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
417 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800418
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700419 if (mIsRtlCompatibilityMode) {
420 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
Alan Viveretteb97d6982015-01-07 16:16:20 -0800421 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
422 mShowing[Drawables.LEFT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700423 mDrawableSizeLeft = mDrawableSizeStart;
424 mDrawableHeightLeft = mDrawableHeightStart;
425 }
426 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
Alan Viveretteb97d6982015-01-07 16:16:20 -0800427 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
428 mShowing[Drawables.RIGHT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700429 mDrawableSizeRight = mDrawableSizeEnd;
430 mDrawableHeightRight = mDrawableHeightEnd;
431 }
432 } else {
433 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
434 // drawable if and only if they have been defined
435 switch(layoutDirection) {
436 case LAYOUT_DIRECTION_RTL:
437 if (mOverride) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800438 mShowing[Drawables.RIGHT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700439 mDrawableSizeRight = mDrawableSizeStart;
440 mDrawableHeightRight = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800441
Alan Viveretteb97d6982015-01-07 16:16:20 -0800442 mShowing[Drawables.LEFT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700443 mDrawableSizeLeft = mDrawableSizeEnd;
444 mDrawableHeightLeft = mDrawableHeightEnd;
445 }
446 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800447
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700448 case LAYOUT_DIRECTION_LTR:
449 default:
450 if (mOverride) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800451 mShowing[Drawables.LEFT] = mDrawableStart;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700452 mDrawableSizeLeft = mDrawableSizeStart;
453 mDrawableHeightLeft = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800454
Alan Viveretteb97d6982015-01-07 16:16:20 -0800455 mShowing[Drawables.RIGHT] = mDrawableEnd;
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700456 mDrawableSizeRight = mDrawableSizeEnd;
457 mDrawableHeightRight = mDrawableHeightEnd;
458 }
459 break;
460 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800461 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800462
Alan Viverette189d4f52015-12-16 16:06:23 -0500463 applyErrorDrawableIfNeeded(layoutDirection);
464
465 return mShowing[Drawables.LEFT] != previousLeft
466 || mShowing[Drawables.RIGHT] != previousRight;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800467 }
468
469 public void setErrorDrawable(Drawable dr, TextView tv) {
470 if (mDrawableError != dr && mDrawableError != null) {
471 mDrawableError.setCallback(null);
472 }
473 mDrawableError = dr;
474
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800475 if (mDrawableError != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -0800476 final Rect compoundRect = mCompoundRect;
477 final int[] state = tv.getDrawableState();
478
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800479 mDrawableError.setState(state);
480 mDrawableError.copyBounds(compoundRect);
481 mDrawableError.setCallback(tv);
482 mDrawableSizeError = compoundRect.width();
483 mDrawableHeightError = compoundRect.height();
484 } else {
485 mDrawableSizeError = mDrawableHeightError = 0;
486 }
487 }
488
489 private void applyErrorDrawableIfNeeded(int layoutDirection) {
490 // first restore the initial state if needed
491 switch (mDrawableSaved) {
492 case DRAWABLE_LEFT:
Alan Viveretteb97d6982015-01-07 16:16:20 -0800493 mShowing[Drawables.LEFT] = mDrawableTemp;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800494 mDrawableSizeLeft = mDrawableSizeTemp;
495 mDrawableHeightLeft = mDrawableHeightTemp;
496 break;
497 case DRAWABLE_RIGHT:
Alan Viveretteb97d6982015-01-07 16:16:20 -0800498 mShowing[Drawables.RIGHT] = mDrawableTemp;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800499 mDrawableSizeRight = mDrawableSizeTemp;
500 mDrawableHeightRight = mDrawableHeightTemp;
501 break;
502 case DRAWABLE_NONE:
503 default:
504 }
505 // then, if needed, assign the Error drawable to the correct location
506 if (mDrawableError != null) {
507 switch(layoutDirection) {
508 case LAYOUT_DIRECTION_RTL:
509 mDrawableSaved = DRAWABLE_LEFT;
510
Alan Viveretteb97d6982015-01-07 16:16:20 -0800511 mDrawableTemp = mShowing[Drawables.LEFT];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800512 mDrawableSizeTemp = mDrawableSizeLeft;
513 mDrawableHeightTemp = mDrawableHeightLeft;
514
Alan Viveretteb97d6982015-01-07 16:16:20 -0800515 mShowing[Drawables.LEFT] = mDrawableError;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800516 mDrawableSizeLeft = mDrawableSizeError;
517 mDrawableHeightLeft = mDrawableHeightError;
518 break;
519 case LAYOUT_DIRECTION_LTR:
520 default:
521 mDrawableSaved = DRAWABLE_RIGHT;
522
Alan Viveretteb97d6982015-01-07 16:16:20 -0800523 mDrawableTemp = mShowing[Drawables.RIGHT];
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800524 mDrawableSizeTemp = mDrawableSizeRight;
525 mDrawableHeightTemp = mDrawableHeightRight;
526
Alan Viveretteb97d6982015-01-07 16:16:20 -0800527 mShowing[Drawables.RIGHT] = mDrawableError;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800528 mDrawableSizeRight = mDrawableSizeError;
529 mDrawableHeightRight = mDrawableHeightError;
530 break;
531 }
532 }
533 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800534 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800535
Gilles Debunned88876a2012-03-16 17:34:04 -0700536 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800537
538 private CharWrapper mCharWrapper;
539
540 private Marquee mMarquee;
541 private boolean mRestartMarquee;
542
543 private int mMarqueeRepeatLimit = 3;
544
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700545 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800546
547 /**
548 * On some devices the fading edges add a performance penalty if used
549 * extensively in the same layout. This mode indicates how the marquee
550 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
551 */
552 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
553
554 /**
555 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
556 * the layout that should be used when the mode switches.
557 */
558 private Layout mSavedMarqueeModeLayout;
559
560 @ViewDebug.ExportedProperty(category = "text")
561 private CharSequence mText;
562 private CharSequence mTransformed;
563 private BufferType mBufferType = BufferType.NORMAL;
564
565 private CharSequence mHint;
566 private Layout mHintLayout;
567
568 private MovementMethod mMovement;
569
570 private TransformationMethod mTransformation;
571 private boolean mAllowTransformationLengthChange;
572 private ChangeWatcher mChangeWatcher;
573
574 private ArrayList<TextWatcher> mListeners;
575
576 // display attributes
577 private final TextPaint mTextPaint;
578 private boolean mUserSetTextScaleX;
579 private Layout mLayout;
Roozbeh Pournadera23748a2015-08-31 14:30:36 -0700580 private boolean mLocalesChanged = false;
Gilles Debunne60e21862012-01-30 15:04:14 -0800581
Alan Viverette816aa142015-04-10 15:41:10 -0700582 @ViewDebug.ExportedProperty(category = "text")
Gilles Debunne60e21862012-01-30 15:04:14 -0800583 private int mGravity = Gravity.TOP | Gravity.START;
584 private boolean mHorizontallyScrolling;
585
586 private int mAutoLinkMask;
587 private boolean mLinksClickable = true;
588
589 private float mSpacingMult = 1.0f;
590 private float mSpacingAdd = 0.0f;
591
Raph Levien39b4db72015-03-25 13:18:20 -0700592 private int mBreakStrategy;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700593 private int mHyphenationFrequency;
Raph Levien39b4db72015-03-25 13:18:20 -0700594
Gilles Debunne60e21862012-01-30 15:04:14 -0800595 private int mMaximum = Integer.MAX_VALUE;
596 private int mMaxMode = LINES;
597 private int mMinimum = 0;
598 private int mMinMode = LINES;
599
600 private int mOldMaximum = mMaximum;
601 private int mOldMaxMode = mMaxMode;
602
603 private int mMaxWidth = Integer.MAX_VALUE;
604 private int mMaxWidthMode = PIXELS;
605 private int mMinWidth = 0;
606 private int mMinWidthMode = PIXELS;
607
608 private boolean mSingleLine;
609 private int mDesiredHeightAtMeasure = -1;
610 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700611 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800612
613 // tmp primitives, so we don't alloc them on each draw
614 private Rect mTempRect;
615 private long mLastScroll;
616 private Scroller mScroller;
617
618 private BoringLayout.Metrics mBoring, mHintBoring;
619 private BoringLayout mSavedLayout, mSavedHintLayout;
620
621 private TextDirectionHeuristic mTextDir;
622
623 private InputFilter[] mFilters = NO_FILTERS;
624
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900625 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900626
Gilles Debunne83051b82012-02-24 20:01:13 -0800627 // It is possible to have a selection even when mEditor is null (programmatically set, like when
628 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700629 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800630 private Path mHighlightPath;
631 private final Paint mHighlightPaint;
632 private boolean mHighlightPathBogus = true;
633
Gilles Debunne60e21862012-01-30 15:04:14 -0800634 // Although these fields are specific to editable text, they are not added to Editor because
635 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700636 int mCursorDrawableRes;
Seigo Nonakaa71a2442015-06-19 15:00:43 +0900637 // These six fields, could be moved to Editor, since we know their default values and we
Gilles Debunne60e21862012-01-30 15:04:14 -0800638 // could condition the creation of the Editor to a non standard value. This is however
639 // brittle since the hardcoded values here (such as
640 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
641 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700642 int mTextSelectHandleLeftRes;
643 int mTextSelectHandleRightRes;
644 int mTextSelectHandleRes;
645 int mTextEditSuggestionItemLayout;
Seigo Nonakaa71a2442015-06-19 15:00:43 +0900646 int mTextEditSuggestionContainerLayout;
647 int mTextEditSuggestionHighlightStyle;
Gilles Debunne60e21862012-01-30 15:04:14 -0800648
649 /**
650 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700651 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800652 */
653 private Editor mEditor;
654
Adam Powellbd918aad2016-01-21 10:49:39 -0800655 private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
656 private static final int DEVICE_PROVISIONED_NO = 1;
657 private static final int DEVICE_PROVISIONED_YES = 2;
658
659 /**
660 * Some special options such as sharing selected text should only be shown if the device
661 * is provisioned. Only check the provisioned state once for a given view instance.
662 */
663 private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
664
Andreas Gampeddc13972016-02-26 16:54:59 -0800665 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 * Kick-start the font cache for the zygote process (to pay the cost of
667 * initializing freetype for our default font only once).
Andreas Gampeddc13972016-02-26 16:54:59 -0800668 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 */
Andreas Gampeddc13972016-02-26 16:54:59 -0800670 public static void preloadFontCache() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800671 Paint p = new Paint();
672 p.setAntiAlias(true);
673 // We don't care about the result, just the side-effect of measuring.
674 p.measureText("H");
675 }
676
677 /**
678 * Interface definition for a callback to be invoked when an action is
679 * performed on the editor.
680 */
681 public interface OnEditorActionListener {
682 /**
683 * Called when an action is being performed.
684 *
685 * @param v The view that was clicked.
686 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700687 * identifier you supplied, or {@link EditorInfo#IME_NULL
688 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 * being pressed.
690 * @param event If triggered by an enter key, this is the event;
691 * otherwise, this is null.
692 * @return Return true if you have consumed the action, else false.
693 */
694 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
695 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 public TextView(Context context) {
698 this(context, null);
699 }
700
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800701 public TextView(Context context, @Nullable AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800702 this(context, attrs, com.android.internal.R.attr.textViewStyle);
703 }
704
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800705 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
Alan Viverette617feb92013-09-09 18:09:13 -0700706 this(context, attrs, defStyleAttr, 0);
707 }
708
Gilles Debunnee15b3582010-06-16 15:17:21 -0700709 @SuppressWarnings("deprecation")
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800710 public TextView(
711 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
Alan Viverette617feb92013-09-09 18:09:13 -0700712 super(context, attrs, defStyleAttr, defStyleRes);
713
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 mText = "";
715
Christopher Tate1373a8e2011-11-10 19:59:13 -0800716 final Resources res = getResources();
717 final CompatibilityInfo compat = res.getCompatibilityInfo();
718
Chris Craik6a49dde2015-05-12 10:28:14 -0700719 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800720 mTextPaint.density = res.getDisplayMetrics().density;
721 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800722
Chris Craik6a49dde2015-05-12 10:28:14 -0700723 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Gilles Debunne83051b82012-02-24 20:01:13 -0800724 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
725
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800727
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800728 mTransformation = null;
729
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 int textColorHighlight = 0;
731 ColorStateList textColor = null;
732 ColorStateList textColorHint = null;
733 ColorStateList textColorLink = null;
734 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700735 String fontFamily = null;
Raph Levien42b30242015-01-29 12:49:19 -0800736 boolean fontFamilyExplicit = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 int typefaceIndex = -1;
738 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700739 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800740 int shadowcolor = 0;
741 float dx = 0, dy = 0, r = 0;
Raph Levien53c00772014-04-14 14:11:02 -0700742 boolean elegant = false;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400743 float letterSpacing = 0;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400744 String fontFeatureSettings = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700745 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700746 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700748 final Resources.Theme theme = context.getTheme();
749
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 /*
751 * Look the appearance up without checking first if it exists because
752 * almost every TextView has one and it greatly simplifies the logic
753 * to be able to parse the appearance first and then let specific tags
754 * for this View override it.
755 */
Alan Viverette617feb92013-09-09 18:09:13 -0700756 TypedArray a = theme.obtainStyledAttributes(attrs,
757 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700759 int ap = a.getResourceId(
760 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
761 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800762 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700763 appearance = theme.obtainStyledAttributes(
764 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800765 }
766 if (appearance != null) {
767 int n = appearance.getIndexCount();
768 for (int i = 0; i < n; i++) {
769 int attr = appearance.getIndex(i);
770
771 switch (attr) {
772 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
773 textColorHighlight = appearance.getColor(attr, textColorHighlight);
774 break;
775
776 case com.android.internal.R.styleable.TextAppearance_textColor:
777 textColor = appearance.getColorStateList(attr);
778 break;
779
780 case com.android.internal.R.styleable.TextAppearance_textColorHint:
781 textColorHint = appearance.getColorStateList(attr);
782 break;
783
784 case com.android.internal.R.styleable.TextAppearance_textColorLink:
785 textColorLink = appearance.getColorStateList(attr);
786 break;
787
788 case com.android.internal.R.styleable.TextAppearance_textSize:
789 textSize = appearance.getDimensionPixelSize(attr, textSize);
790 break;
791
792 case com.android.internal.R.styleable.TextAppearance_typeface:
793 typefaceIndex = appearance.getInt(attr, -1);
794 break;
795
Raph Leviend570e892012-05-09 11:45:34 -0700796 case com.android.internal.R.styleable.TextAppearance_fontFamily:
797 fontFamily = appearance.getString(attr);
798 break;
799
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800800 case com.android.internal.R.styleable.TextAppearance_textStyle:
801 styleIndex = appearance.getInt(attr, -1);
802 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700803
804 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
805 allCaps = appearance.getBoolean(attr, false);
806 break;
Adam Powellac91df82013-02-14 13:48:47 -0800807
808 case com.android.internal.R.styleable.TextAppearance_shadowColor:
Raph Levien202d1ec2014-04-14 11:20:22 -0700809 shadowcolor = appearance.getInt(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800810 break;
811
812 case com.android.internal.R.styleable.TextAppearance_shadowDx:
Raph Levien202d1ec2014-04-14 11:20:22 -0700813 dx = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800814 break;
815
816 case com.android.internal.R.styleable.TextAppearance_shadowDy:
Raph Levien202d1ec2014-04-14 11:20:22 -0700817 dy = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800818 break;
819
820 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
Raph Levien202d1ec2014-04-14 11:20:22 -0700821 r = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800822 break;
Raph Levien53c00772014-04-14 14:11:02 -0700823
824 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
825 elegant = appearance.getBoolean(attr, false);
826 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400827
828 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
829 letterSpacing = appearance.getFloat(attr, 0);
830 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400831
832 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
833 fontFeatureSettings = appearance.getString(attr);
834 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835 }
836 }
837
838 appearance.recycle();
839 }
840
841 boolean editable = getDefaultEditable();
842 CharSequence inputMethod = null;
843 int numeric = 0;
844 CharSequence digits = null;
845 boolean phone = false;
846 boolean autotext = false;
847 int autocap = -1;
848 int buffertype = 0;
849 boolean selectallonfocus = false;
850 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700851 drawableBottom = null, drawableStart = null, drawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -0800852 ColorStateList drawableTint = null;
853 PorterDuff.Mode drawableTintMode = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800854 int drawablePadding = 0;
855 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700856 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 int maxlength = -1;
858 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700859 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800860 boolean password = false;
861 int inputType = EditorInfo.TYPE_NULL;
862
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700863 a = theme.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -0700864 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700865
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 int n = a.getIndexCount();
867 for (int i = 0; i < n; i++) {
868 int attr = a.getIndex(i);
869
870 switch (attr) {
871 case com.android.internal.R.styleable.TextView_editable:
872 editable = a.getBoolean(attr, editable);
873 break;
874
875 case com.android.internal.R.styleable.TextView_inputMethod:
876 inputMethod = a.getText(attr);
877 break;
878
879 case com.android.internal.R.styleable.TextView_numeric:
880 numeric = a.getInt(attr, numeric);
881 break;
882
883 case com.android.internal.R.styleable.TextView_digits:
884 digits = a.getText(attr);
885 break;
886
887 case com.android.internal.R.styleable.TextView_phoneNumber:
888 phone = a.getBoolean(attr, phone);
889 break;
890
891 case com.android.internal.R.styleable.TextView_autoText:
892 autotext = a.getBoolean(attr, autotext);
893 break;
894
895 case com.android.internal.R.styleable.TextView_capitalize:
896 autocap = a.getInt(attr, autocap);
897 break;
898
899 case com.android.internal.R.styleable.TextView_bufferType:
900 buffertype = a.getInt(attr, buffertype);
901 break;
902
903 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
904 selectallonfocus = a.getBoolean(attr, selectallonfocus);
905 break;
906
907 case com.android.internal.R.styleable.TextView_autoLink:
908 mAutoLinkMask = a.getInt(attr, 0);
909 break;
910
911 case com.android.internal.R.styleable.TextView_linksClickable:
912 mLinksClickable = a.getBoolean(attr, true);
913 break;
914
915 case com.android.internal.R.styleable.TextView_drawableLeft:
916 drawableLeft = a.getDrawable(attr);
917 break;
918
919 case com.android.internal.R.styleable.TextView_drawableTop:
920 drawableTop = a.getDrawable(attr);
921 break;
922
923 case com.android.internal.R.styleable.TextView_drawableRight:
924 drawableRight = a.getDrawable(attr);
925 break;
926
927 case com.android.internal.R.styleable.TextView_drawableBottom:
928 drawableBottom = a.getDrawable(attr);
929 break;
930
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700931 case com.android.internal.R.styleable.TextView_drawableStart:
932 drawableStart = a.getDrawable(attr);
933 break;
934
935 case com.android.internal.R.styleable.TextView_drawableEnd:
936 drawableEnd = a.getDrawable(attr);
937 break;
938
Alan Viveretteb97d6982015-01-07 16:16:20 -0800939 case com.android.internal.R.styleable.TextView_drawableTint:
940 drawableTint = a.getColorStateList(attr);
941 break;
942
943 case com.android.internal.R.styleable.TextView_drawableTintMode:
944 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
945 break;
946
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 case com.android.internal.R.styleable.TextView_drawablePadding:
948 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
949 break;
950
951 case com.android.internal.R.styleable.TextView_maxLines:
952 setMaxLines(a.getInt(attr, -1));
953 break;
954
955 case com.android.internal.R.styleable.TextView_maxHeight:
956 setMaxHeight(a.getDimensionPixelSize(attr, -1));
957 break;
958
959 case com.android.internal.R.styleable.TextView_lines:
960 setLines(a.getInt(attr, -1));
961 break;
962
963 case com.android.internal.R.styleable.TextView_height:
964 setHeight(a.getDimensionPixelSize(attr, -1));
965 break;
966
967 case com.android.internal.R.styleable.TextView_minLines:
968 setMinLines(a.getInt(attr, -1));
969 break;
970
971 case com.android.internal.R.styleable.TextView_minHeight:
972 setMinHeight(a.getDimensionPixelSize(attr, -1));
973 break;
974
975 case com.android.internal.R.styleable.TextView_maxEms:
976 setMaxEms(a.getInt(attr, -1));
977 break;
978
979 case com.android.internal.R.styleable.TextView_maxWidth:
980 setMaxWidth(a.getDimensionPixelSize(attr, -1));
981 break;
982
983 case com.android.internal.R.styleable.TextView_ems:
984 setEms(a.getInt(attr, -1));
985 break;
986
987 case com.android.internal.R.styleable.TextView_width:
988 setWidth(a.getDimensionPixelSize(attr, -1));
989 break;
990
991 case com.android.internal.R.styleable.TextView_minEms:
992 setMinEms(a.getInt(attr, -1));
993 break;
994
995 case com.android.internal.R.styleable.TextView_minWidth:
996 setMinWidth(a.getDimensionPixelSize(attr, -1));
997 break;
998
999 case com.android.internal.R.styleable.TextView_gravity:
1000 setGravity(a.getInt(attr, -1));
1001 break;
1002
1003 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -07001004 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 break;
1006
1007 case com.android.internal.R.styleable.TextView_text:
1008 text = a.getText(attr);
1009 break;
1010
1011 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1012 if (a.getBoolean(attr, false)) {
1013 setHorizontallyScrolling(true);
1014 }
1015 break;
1016
1017 case com.android.internal.R.styleable.TextView_singleLine:
1018 singleLine = a.getBoolean(attr, singleLine);
1019 break;
1020
1021 case com.android.internal.R.styleable.TextView_ellipsize:
1022 ellipsize = a.getInt(attr, ellipsize);
1023 break;
1024
1025 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1026 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1027 break;
1028
1029 case com.android.internal.R.styleable.TextView_includeFontPadding:
1030 if (!a.getBoolean(attr, true)) {
1031 setIncludeFontPadding(false);
1032 }
1033 break;
1034
1035 case com.android.internal.R.styleable.TextView_cursorVisible:
1036 if (!a.getBoolean(attr, true)) {
1037 setCursorVisible(false);
1038 }
1039 break;
1040
1041 case com.android.internal.R.styleable.TextView_maxLength:
1042 maxlength = a.getInt(attr, -1);
1043 break;
1044
1045 case com.android.internal.R.styleable.TextView_textScaleX:
1046 setTextScaleX(a.getFloat(attr, 1.0f));
1047 break;
1048
1049 case com.android.internal.R.styleable.TextView_freezesText:
1050 mFreezesText = a.getBoolean(attr, false);
1051 break;
1052
1053 case com.android.internal.R.styleable.TextView_shadowColor:
1054 shadowcolor = a.getInt(attr, 0);
1055 break;
1056
1057 case com.android.internal.R.styleable.TextView_shadowDx:
1058 dx = a.getFloat(attr, 0);
1059 break;
1060
1061 case com.android.internal.R.styleable.TextView_shadowDy:
1062 dy = a.getFloat(attr, 0);
1063 break;
1064
1065 case com.android.internal.R.styleable.TextView_shadowRadius:
1066 r = a.getFloat(attr, 0);
1067 break;
1068
1069 case com.android.internal.R.styleable.TextView_enabled:
1070 setEnabled(a.getBoolean(attr, isEnabled()));
1071 break;
1072
1073 case com.android.internal.R.styleable.TextView_textColorHighlight:
1074 textColorHighlight = a.getColor(attr, textColorHighlight);
1075 break;
1076
1077 case com.android.internal.R.styleable.TextView_textColor:
1078 textColor = a.getColorStateList(attr);
1079 break;
1080
1081 case com.android.internal.R.styleable.TextView_textColorHint:
1082 textColorHint = a.getColorStateList(attr);
1083 break;
1084
1085 case com.android.internal.R.styleable.TextView_textColorLink:
1086 textColorLink = a.getColorStateList(attr);
1087 break;
1088
1089 case com.android.internal.R.styleable.TextView_textSize:
1090 textSize = a.getDimensionPixelSize(attr, textSize);
1091 break;
1092
1093 case com.android.internal.R.styleable.TextView_typeface:
1094 typefaceIndex = a.getInt(attr, typefaceIndex);
1095 break;
1096
1097 case com.android.internal.R.styleable.TextView_textStyle:
1098 styleIndex = a.getInt(attr, styleIndex);
1099 break;
1100
Raph Leviend570e892012-05-09 11:45:34 -07001101 case com.android.internal.R.styleable.TextView_fontFamily:
1102 fontFamily = a.getString(attr);
Raph Levien42b30242015-01-29 12:49:19 -08001103 fontFamilyExplicit = true;
Raph Leviend570e892012-05-09 11:45:34 -07001104 break;
1105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 case com.android.internal.R.styleable.TextView_password:
1107 password = a.getBoolean(attr, password);
1108 break;
1109
1110 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1111 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1112 break;
1113
1114 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1115 mSpacingMult = a.getFloat(attr, mSpacingMult);
1116 break;
1117
1118 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001119 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 break;
1121
James Cookf1dad1e2015-02-27 11:00:01 -08001122 case com.android.internal.R.styleable.TextView_allowUndo:
1123 createEditorIfNeeded();
1124 mEditor.mAllowUndo = a.getBoolean(attr, true);
1125 break;
1126
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001127 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001128 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001129 mEditor.createInputContentTypeIfNeeded();
1130 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1131 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 break;
1133
1134 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001135 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001136 mEditor.createInputContentTypeIfNeeded();
1137 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 break;
1139
1140 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001141 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001142 mEditor.createInputContentTypeIfNeeded();
1143 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1144 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001145 break;
1146
1147 case com.android.internal.R.styleable.TextView_privateImeOptions:
1148 setPrivateImeOptions(a.getString(attr));
1149 break;
1150
1151 case com.android.internal.R.styleable.TextView_editorExtras:
1152 try {
1153 setInputExtras(a.getResourceId(attr, 0));
1154 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001155 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001156 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001157 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001158 }
1159 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001160
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001161 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1162 mCursorDrawableRes = a.getResourceId(attr, 0);
1163 break;
1164
Adam Powellb08013c2010-09-16 16:28:11 -07001165 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1166 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1167 break;
1168
1169 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1170 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1171 break;
1172
1173 case com.android.internal.R.styleable.TextView_textSelectHandle:
1174 mTextSelectHandleRes = a.getResourceId(attr, 0);
1175 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001176
Gilles Debunne69340442011-03-31 13:37:51 -07001177 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1178 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1179 break;
1180
Seigo Nonakaa71a2442015-06-19 15:00:43 +09001181 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1182 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1183 break;
1184
1185 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1186 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1187 break;
1188
Gilles Debunne86b9c782010-11-11 10:43:48 -08001189 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001190 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001191 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001192
Adam Powell7f8f79a2011-07-07 18:35:54 -07001193 case com.android.internal.R.styleable.TextView_textAllCaps:
1194 allCaps = a.getBoolean(attr, false);
1195 break;
Raph Levien53c00772014-04-14 14:11:02 -07001196
1197 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1198 elegant = a.getBoolean(attr, false);
1199 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001200
1201 case com.android.internal.R.styleable.TextView_letterSpacing:
1202 letterSpacing = a.getFloat(attr, 0);
1203 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001204
1205 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1206 fontFeatureSettings = a.getString(attr);
1207 break;
Raph Levien39b4db72015-03-25 13:18:20 -07001208
1209 case com.android.internal.R.styleable.TextView_breakStrategy:
1210 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
Raph Leviene319d5a2015-04-14 23:51:07 -07001211 break;
1212
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07001213 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1214 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1215 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 }
1217 }
1218 a.recycle();
1219
1220 BufferType bufferType = BufferType.EDITABLE;
1221
Gilles Debunned7483bf2010-11-10 10:47:45 -08001222 final int variation =
1223 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1224 final boolean passwordInputType = variation
1225 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1226 final boolean webPasswordInputType = variation
1227 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001228 final boolean numberPasswordInputType = variation
1229 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001232 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233
1234 try {
1235 c = Class.forName(inputMethod.toString());
1236 } catch (ClassNotFoundException ex) {
1237 throw new RuntimeException(ex);
1238 }
1239
1240 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001241 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001242 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001243 } catch (InstantiationException ex) {
1244 throw new RuntimeException(ex);
1245 } catch (IllegalAccessException ex) {
1246 throw new RuntimeException(ex);
1247 }
1248 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001249 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001251 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001253 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 }
1255 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001256 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001257 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001258 // If no input type was specified, we will default to generic
1259 // text, since we can't tell the IME about the set of digits
1260 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001261 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001262 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001263 } else if (inputType != EditorInfo.TYPE_NULL) {
1264 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001265 // If set, the input type overrides what was set using the deprecated singleLine flag.
1266 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001267 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001268 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001269 mEditor.mKeyListener = DialerKeyListener.getInstance();
1270 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001272 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001273 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 (numeric & DECIMAL) != 0);
1275 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1276 if ((numeric & SIGNED) != 0) {
1277 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1278 }
1279 if ((numeric & DECIMAL) != 0) {
1280 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1281 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001282 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 } else if (autotext || autocap != -1) {
1284 TextKeyListener.Capitalize cap;
1285
1286 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001288 switch (autocap) {
1289 case 1:
1290 cap = TextKeyListener.Capitalize.SENTENCES;
1291 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1292 break;
1293
1294 case 2:
1295 cap = TextKeyListener.Capitalize.WORDS;
1296 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1297 break;
1298
1299 case 3:
1300 cap = TextKeyListener.Capitalize.CHARACTERS;
1301 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1302 break;
1303
1304 default:
1305 cap = TextKeyListener.Capitalize.NONE;
1306 break;
1307 }
1308
Gilles Debunne5fae9962012-05-08 14:53:20 -07001309 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001310 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1311 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001312 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001313 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001314 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001315 mEditor.mKeyListener = null;
1316 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001317 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001318 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001319 // So that selection can be changed using arrow keys and touch is handled.
1320 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001321 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001322 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001323 mEditor.mKeyListener = TextKeyListener.getInstance();
1324 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001326 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327
1328 switch (buffertype) {
1329 case 0:
1330 bufferType = BufferType.NORMAL;
1331 break;
1332 case 1:
1333 bufferType = BufferType.SPANNABLE;
1334 break;
1335 case 2:
1336 bufferType = BufferType.EDITABLE;
1337 break;
1338 }
1339 }
1340
Gilles Debunne2d373a12012-04-20 15:32:19 -07001341 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1342 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001343
1344 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001345 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001346 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001347
1348 if (bufferType == BufferType.NORMAL)
1349 bufferType = BufferType.SPANNABLE;
1350 }
1351
Alan Viveretteb97d6982015-01-07 16:16:20 -08001352 // Set up the tint (if needed) before setting the drawables so that it
1353 // gets applied correctly.
1354 if (drawableTint != null || drawableTintMode != null) {
1355 if (mDrawables == null) {
1356 mDrawables = new Drawables(context);
1357 }
1358 if (drawableTint != null) {
1359 mDrawables.mTintList = drawableTint;
1360 mDrawables.mHasTint = true;
1361 }
1362 if (drawableTintMode != null) {
1363 mDrawables.mTintMode = drawableTintMode;
1364 mDrawables.mHasTintMode = true;
1365 }
1366 }
1367
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001368 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001369 setCompoundDrawablesWithIntrinsicBounds(
1370 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001371 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001372 setCompoundDrawablePadding(drawablePadding);
1373
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001374 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001375 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001376 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001377 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001378
Gilles Debunne60e21862012-01-30 15:04:14 -08001379 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001381 }
1382
1383 switch (ellipsize) {
1384 case 1:
1385 setEllipsize(TextUtils.TruncateAt.START);
1386 break;
1387 case 2:
1388 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1389 break;
1390 case 3:
1391 setEllipsize(TextUtils.TruncateAt.END);
1392 break;
1393 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001394 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1395 setHorizontalFadingEdgeEnabled(true);
1396 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1397 } else {
1398 setHorizontalFadingEdgeEnabled(false);
1399 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1400 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001401 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1402 break;
1403 }
1404
1405 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1406 setHintTextColor(textColorHint);
1407 setLinkTextColor(textColorLink);
1408 if (textColorHighlight != 0) {
1409 setHighlightColor(textColorHighlight);
1410 }
1411 setRawTextSize(textSize);
Raph Levien53c00772014-04-14 14:11:02 -07001412 setElegantTextHeight(elegant);
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001413 setLetterSpacing(letterSpacing);
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001414 setFontFeatureSettings(fontFeatureSettings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415
Adam Powell7f8f79a2011-07-07 18:35:54 -07001416 if (allCaps) {
1417 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1418 }
1419
Ken Wakasa82d731a2010-12-24 23:42:41 +09001420 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001421 setTransformationMethod(PasswordTransformationMethod.getInstance());
1422 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001423 } else if (mEditor != null &&
1424 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001425 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001426 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001427 }
1428
Raph Levien42b30242015-01-29 12:49:19 -08001429 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1430 fontFamily = null;
1431 }
Raph Leviend570e892012-05-09 11:45:34 -07001432 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001433
1434 if (shadowcolor != 0) {
1435 setShadowLayer(r, dx, dy, shadowcolor);
1436 }
1437
1438 if (maxlength >= 0) {
1439 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1440 } else {
1441 setFilters(NO_FILTERS);
1442 }
1443
1444 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001445 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001446
1447 /*
1448 * Views are not normally focusable unless specified to be.
1449 * However, TextViews that have input or movement methods *are*
1450 * focusable by default.
1451 */
Alan Viverette617feb92013-09-09 18:09:13 -07001452 a = context.obtainStyledAttributes(
1453 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001454
Gilles Debunne60e21862012-01-30 15:04:14 -08001455 boolean focusable = mMovement != null || getKeyListener() != null;
Alan Viverettef32efeb2014-08-14 14:03:21 -07001456 boolean clickable = focusable || isClickable();
1457 boolean longClickable = focusable || isLongClickable();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001458
1459 n = a.getIndexCount();
1460 for (int i = 0; i < n; i++) {
1461 int attr = a.getIndex(i);
1462
1463 switch (attr) {
1464 case com.android.internal.R.styleable.View_focusable:
1465 focusable = a.getBoolean(attr, focusable);
1466 break;
1467
1468 case com.android.internal.R.styleable.View_clickable:
1469 clickable = a.getBoolean(attr, clickable);
1470 break;
1471
1472 case com.android.internal.R.styleable.View_longClickable:
1473 longClickable = a.getBoolean(attr, longClickable);
1474 break;
1475 }
1476 }
1477 a.recycle();
1478
1479 setFocusable(focusable);
1480 setClickable(clickable);
1481 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001482
Gilles Debunned88876a2012-03-16 17:34:04 -07001483 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001484
1485 // If not explicitly specified this view is important for accessibility.
1486 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1487 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1488 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489 }
1490
Raph Leviene319d5a2015-04-14 23:51:07 -07001491 private int[] parseDimensionArray(TypedArray dimens) {
1492 if (dimens == null) {
1493 return null;
1494 }
1495 int[] result = new int[dimens.length()];
1496 for (int i = 0; i < result.length; i++) {
1497 result[i] = dimens.getDimensionPixelSize(i, 0);
1498 }
1499 return result;
1500 }
1501
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001502 /**
1503 * @hide
1504 */
1505 @Override
1506 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1507 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001508 if (resultCode == Activity.RESULT_OK && data != null) {
1509 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1510 if (result != null) {
1511 if (isTextEditable()) {
1512 replaceSelectionWithText(result);
1513 } else {
1514 if (result.length() > 0) {
1515 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1516 .show();
1517 }
1518 }
Andrei Stingaceanu6154b092015-04-20 14:53:47 +01001519 }
Abodunrinwa Toki3b8a5ea2015-10-20 00:19:23 +01001520 } else if (mText instanceof Spannable) {
1521 // Reset the selection.
1522 stopTextActionMode();
1523 Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001524 }
Abodunrinwa Toki3b8a5ea2015-10-20 00:19:23 +01001525
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001526 if (mEditor.hasSelectionController()) {
Clara Bayarri7938cdb2015-06-02 20:03:45 +01001527 mEditor.startSelectionActionMode();
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001528 }
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001529 }
1530 }
1531
Raph Leviend570e892012-05-09 11:45:34 -07001532 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001533 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001534 if (familyName != null) {
1535 tf = Typeface.create(familyName, styleIndex);
1536 if (tf != null) {
1537 setTypeface(tf);
1538 return;
1539 }
1540 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001541 switch (typefaceIndex) {
1542 case SANS:
1543 tf = Typeface.SANS_SERIF;
1544 break;
1545
1546 case SERIF:
1547 tf = Typeface.SERIF;
1548 break;
1549
1550 case MONOSPACE:
1551 tf = Typeface.MONOSPACE;
1552 break;
1553 }
1554
1555 setTypeface(tf, styleIndex);
1556 }
1557
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001558 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1559 boolean hasRelativeDrawables = (start != null) || (end != null);
1560 if (hasRelativeDrawables) {
1561 Drawables dr = mDrawables;
1562 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001563 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001564 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001565 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001566 final Rect compoundRect = dr.mCompoundRect;
1567 int[] state = getDrawableState();
1568 if (start != null) {
1569 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1570 start.setState(state);
1571 start.copyBounds(compoundRect);
1572 start.setCallback(this);
1573
1574 dr.mDrawableStart = start;
1575 dr.mDrawableSizeStart = compoundRect.width();
1576 dr.mDrawableHeightStart = compoundRect.height();
1577 } else {
1578 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1579 }
1580 if (end != null) {
1581 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1582 end.setState(state);
1583 end.copyBounds(compoundRect);
1584 end.setCallback(this);
1585
1586 dr.mDrawableEnd = end;
1587 dr.mDrawableSizeEnd = compoundRect.width();
1588 dr.mDrawableHeightEnd = compoundRect.height();
1589 } else {
1590 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1591 }
Fabrice Di Meglio4155e2e2013-08-08 16:28:07 -07001592 resetResolvedDrawables();
1593 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08001594 applyCompoundDrawableTint();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001595 }
1596 }
1597
Janos Levai042856c2010-10-15 02:53:58 +03001598 @Override
1599 public void setEnabled(boolean enabled) {
1600 if (enabled == isEnabled()) {
1601 return;
1602 }
1603
1604 if (!enabled) {
1605 // Hide the soft input if the currently active TextView is disabled
1606 InputMethodManager imm = InputMethodManager.peekInstance();
1607 if (imm != null && imm.isActive(this)) {
1608 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1609 }
1610 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001611
Janos Levai042856c2010-10-15 02:53:58 +03001612 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001613
Dianne Hackbornbc823852011-09-18 17:19:50 -07001614 if (enabled) {
1615 // Make sure IME is updated with current editor info.
1616 InputMethodManager imm = InputMethodManager.peekInstance();
1617 if (imm != null) imm.restartInput(this);
1618 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001619
Gilles Debunne33b7de852012-03-12 11:57:48 -07001620 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001621 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001622 mEditor.invalidateTextDisplayList();
1623 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001624
Gilles Debunned88876a2012-03-16 17:34:04 -07001625 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001626 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001627 }
Janos Levai042856c2010-10-15 02:53:58 +03001628 }
1629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001630 /**
1631 * Sets the typeface and style in which the text should be displayed,
1632 * and turns on the fake bold and italic bits in the Paint if the
1633 * Typeface that you provided does not have all the bits in the
1634 * style that you specified.
1635 *
1636 * @attr ref android.R.styleable#TextView_typeface
1637 * @attr ref android.R.styleable#TextView_textStyle
1638 */
1639 public void setTypeface(Typeface tf, int style) {
1640 if (style > 0) {
1641 if (tf == null) {
1642 tf = Typeface.defaultFromStyle(style);
1643 } else {
1644 tf = Typeface.create(tf, style);
1645 }
1646
1647 setTypeface(tf);
1648 // now compute what (if any) algorithmic styling is needed
1649 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1650 int need = style & ~typefaceStyle;
1651 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1652 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1653 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001654 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 mTextPaint.setTextSkewX(0);
1656 setTypeface(tf);
1657 }
1658 }
1659
1660 /**
1661 * Subclasses override this to specify that they have a KeyListener
1662 * by default even if not specifically called for in the XML options.
1663 */
1664 protected boolean getDefaultEditable() {
1665 return false;
1666 }
1667
1668 /**
1669 * Subclasses override this to specify a default movement method.
1670 */
1671 protected MovementMethod getDefaultMovementMethod() {
1672 return null;
1673 }
1674
1675 /**
1676 * Return the text the TextView is displaying. If setText() was called with
1677 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1678 * the return value from this method to Spannable or Editable, respectively.
1679 *
1680 * Note: The content of the return value should not be modified. If you want
1681 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001682 *
1683 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001684 */
1685 @ViewDebug.CapturedViewProperty
1686 public CharSequence getText() {
1687 return mText;
1688 }
1689
1690 /**
1691 * Returns the length, in characters, of the text managed by this TextView
1692 */
1693 public int length() {
1694 return mText.length();
1695 }
1696
1697 /**
1698 * Return the text the TextView is displaying as an Editable object. If
1699 * the text is not editable, null is returned.
1700 *
1701 * @see #getText
1702 */
1703 public Editable getEditableText() {
1704 return (mText instanceof Editable) ? (Editable)mText : null;
1705 }
1706
1707 /**
1708 * @return the height of one standard line in pixels. Note that markup
1709 * within the text can cause individual lines to be taller or shorter
1710 * than this height, and the layout may contain additional first-
1711 * or last-line padding.
1712 */
1713 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001714 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001715 }
1716
1717 /**
1718 * @return the Layout that is currently being used to display the text.
1719 * This can be null if the text or width has recently changes.
1720 */
1721 public final Layout getLayout() {
1722 return mLayout;
1723 }
1724
1725 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001726 * @return the Layout that is currently being used to display the hint text.
1727 * This can be null.
1728 */
1729 final Layout getHintLayout() {
1730 return mHintLayout;
1731 }
1732
1733 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001734 * Retrieve the {@link android.content.UndoManager} that is currently associated
1735 * with this TextView. By default there is no associated UndoManager, so null
1736 * is returned. One can be associated with the TextView through
1737 * {@link #setUndoManager(android.content.UndoManager, String)}
Dianne Hackbornb811e642013-09-04 17:43:56 -07001738 *
1739 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001740 */
1741 public final UndoManager getUndoManager() {
James Cookf59152c2015-02-26 18:03:58 -08001742 // TODO: Consider supporting a global undo manager.
1743 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001744 }
1745
Seigo Nonakaa60160b2015-08-19 12:38:35 -07001746
1747 /**
1748 * @hide
1749 */
1750 @VisibleForTesting
1751 public final Editor getEditorForTesting() {
1752 return mEditor;
1753 }
1754
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001755 /**
1756 * Associate an {@link android.content.UndoManager} with this TextView. Once
1757 * done, all edit operations on the TextView will result in appropriate
1758 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1759 * stack.
1760 *
1761 * @param undoManager The {@link android.content.UndoManager} to associate with
1762 * this TextView, or null to clear any existing association.
1763 * @param tag String tag identifying this particular TextView owner in the
1764 * UndoManager. This is used to keep the correct association with the
1765 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
Dianne Hackbornb811e642013-09-04 17:43:56 -07001766 *
1767 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001768 */
1769 public final void setUndoManager(UndoManager undoManager, String tag) {
James Cookf59152c2015-02-26 18:03:58 -08001770 // TODO: Consider supporting a global undo manager. An implementation will need to:
1771 // * createEditorIfNeeded()
1772 // * Promote to BufferType.EDITABLE if needed.
1773 // * Update the UndoManager and UndoOwner.
1774 // Likewise it will need to be able to restore the default UndoManager.
1775 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001776 }
1777
1778 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001779 * @return the current key listener for this TextView.
1780 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001781 *
1782 * @attr ref android.R.styleable#TextView_numeric
1783 * @attr ref android.R.styleable#TextView_digits
1784 * @attr ref android.R.styleable#TextView_phoneNumber
1785 * @attr ref android.R.styleable#TextView_inputMethod
1786 * @attr ref android.R.styleable#TextView_capitalize
1787 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001788 */
1789 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001790 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001791 }
1792
1793 /**
1794 * Sets the key listener to be used with this TextView. This can be null
1795 * to disallow user input. Note that this method has significant and
1796 * subtle interactions with soft keyboards and other input method:
1797 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1798 * for important details. Calling this method will replace the current
1799 * content type of the text view with the content type returned by the
1800 * key listener.
1801 * <p>
1802 * Be warned that if you want a TextView with a key listener or movement
1803 * method not to be focusable, or if you want a TextView without a
1804 * key listener or movement method to be focusable, you must call
1805 * {@link #setFocusable} again after calling this to get the focusability
1806 * back the way you want it.
1807 *
1808 * @attr ref android.R.styleable#TextView_numeric
1809 * @attr ref android.R.styleable#TextView_digits
1810 * @attr ref android.R.styleable#TextView_phoneNumber
1811 * @attr ref android.R.styleable#TextView_inputMethod
1812 * @attr ref android.R.styleable#TextView_capitalize
1813 * @attr ref android.R.styleable#TextView_autoText
1814 */
1815 public void setKeyListener(KeyListener input) {
1816 setKeyListenerOnly(input);
1817 fixFocusableAndClickableSettings();
1818
1819 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001820 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001821 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001822 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001823 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001824 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001825 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001826 // Change inputType, without affecting transformation.
1827 // No need to applySingleLine since mSingleLine is unchanged.
1828 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001829 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001830 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001831 }
1832
1833 InputMethodManager imm = InputMethodManager.peekInstance();
1834 if (imm != null) imm.restartInput(this);
1835 }
1836
1837 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001838 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001839
Gilles Debunne5fae9962012-05-08 14:53:20 -07001840 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001841 if (mEditor.mKeyListener != input) {
1842 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001843 if (input != null && !(mText instanceof Editable)) {
1844 setText(mText);
1845 }
1846
1847 setFilters((Editable) mText, mFilters);
1848 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001849 }
1850
1851 /**
1852 * @return the movement method being used for this TextView.
1853 * This will frequently be null for non-EditText TextViews.
1854 */
1855 public final MovementMethod getMovementMethod() {
1856 return mMovement;
1857 }
1858
1859 /**
1860 * Sets the movement method (arrow key handler) to be used for
1861 * this TextView. This can be null to disallow using the arrow keys
1862 * to move the cursor or scroll the view.
1863 * <p>
1864 * Be warned that if you want a TextView with a key listener or movement
1865 * method not to be focusable, or if you want a TextView without a
1866 * key listener or movement method to be focusable, you must call
1867 * {@link #setFocusable} again after calling this to get the focusability
1868 * back the way you want it.
1869 */
1870 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001871 if (mMovement != movement) {
1872 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001873
Gilles Debunne60e21862012-01-30 15:04:14 -08001874 if (movement != null && !(mText instanceof Spannable)) {
1875 setText(mText);
1876 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001877
Gilles Debunne60e21862012-01-30 15:04:14 -08001878 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001879
Gilles Debunne2d373a12012-04-20 15:32:19 -07001880 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1881 // mMovement
1882 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001883 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001884 }
1885
1886 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001887 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001888 setFocusable(true);
1889 setClickable(true);
1890 setLongClickable(true);
1891 } else {
1892 setFocusable(false);
1893 setClickable(false);
1894 setLongClickable(false);
1895 }
1896 }
1897
1898 /**
1899 * @return the current transformation method for this TextView.
1900 * This will frequently be null except for single-line and password
1901 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001902 *
1903 * @attr ref android.R.styleable#TextView_password
1904 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001905 */
1906 public final TransformationMethod getTransformationMethod() {
1907 return mTransformation;
1908 }
1909
1910 /**
1911 * Sets the transformation that is applied to the text that this
1912 * TextView is displaying.
1913 *
1914 * @attr ref android.R.styleable#TextView_password
1915 * @attr ref android.R.styleable#TextView_singleLine
1916 */
1917 public final void setTransformationMethod(TransformationMethod method) {
1918 if (method == mTransformation) {
1919 // Avoid the setText() below if the transformation is
1920 // the same.
1921 return;
1922 }
1923 if (mTransformation != null) {
1924 if (mText instanceof Spannable) {
1925 ((Spannable) mText).removeSpan(mTransformation);
1926 }
1927 }
1928
1929 mTransformation = method;
1930
Adam Powell7f8f79a2011-07-07 18:35:54 -07001931 if (method instanceof TransformationMethod2) {
1932 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001933 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001934 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1935 } else {
1936 mAllowTransformationLengthChange = false;
1937 }
1938
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001939 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001940
1941 if (hasPasswordTransformationMethod()) {
Alan Viverette77e9a282013-09-12 17:16:09 -07001942 notifyViewAccessibilityStateChangedIfNeeded(
1943 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001945 }
1946
1947 /**
1948 * Returns the top padding of the view, plus space for the top
1949 * Drawable if any.
1950 */
1951 public int getCompoundPaddingTop() {
1952 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001953 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001954 return mPaddingTop;
1955 } else {
1956 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1957 }
1958 }
1959
1960 /**
1961 * Returns the bottom padding of the view, plus space for the bottom
1962 * Drawable if any.
1963 */
1964 public int getCompoundPaddingBottom() {
1965 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001966 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001967 return mPaddingBottom;
1968 } else {
1969 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1970 }
1971 }
1972
1973 /**
1974 * Returns the left padding of the view, plus space for the left
1975 * Drawable if any.
1976 */
1977 public int getCompoundPaddingLeft() {
1978 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001979 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001980 return mPaddingLeft;
1981 } else {
1982 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1983 }
1984 }
1985
1986 /**
1987 * Returns the right padding of the view, plus space for the right
1988 * Drawable if any.
1989 */
1990 public int getCompoundPaddingRight() {
1991 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001992 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001993 return mPaddingRight;
1994 } else {
1995 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1996 }
1997 }
1998
1999 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002000 * Returns the start padding of the view, plus space for the start
2001 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002002 */
2003 public int getCompoundPaddingStart() {
2004 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07002005 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002006 default:
2007 case LAYOUT_DIRECTION_LTR:
2008 return getCompoundPaddingLeft();
2009 case LAYOUT_DIRECTION_RTL:
2010 return getCompoundPaddingRight();
2011 }
2012 }
2013
2014 /**
2015 * Returns the end padding of the view, plus space for the end
2016 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002017 */
2018 public int getCompoundPaddingEnd() {
2019 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07002020 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002021 default:
2022 case LAYOUT_DIRECTION_LTR:
2023 return getCompoundPaddingRight();
2024 case LAYOUT_DIRECTION_RTL:
2025 return getCompoundPaddingLeft();
2026 }
2027 }
2028
2029 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002030 * Returns the extended top padding of the view, including both the
2031 * top Drawable if any and any extra space to keep more than maxLines
2032 * of text from showing. It is only valid to call this after measuring.
2033 */
2034 public int getExtendedPaddingTop() {
2035 if (mMaxMode != LINES) {
2036 return getCompoundPaddingTop();
2037 }
2038
Raph Levien463cf1a2014-09-02 14:22:23 -07002039 if (mLayout == null) {
2040 assumeLayout();
2041 }
2042
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002043 if (mLayout.getLineCount() <= mMaximum) {
2044 return getCompoundPaddingTop();
2045 }
2046
2047 int top = getCompoundPaddingTop();
2048 int bottom = getCompoundPaddingBottom();
2049 int viewht = getHeight() - top - bottom;
2050 int layoutht = mLayout.getLineTop(mMaximum);
2051
2052 if (layoutht >= viewht) {
2053 return top;
2054 }
2055
2056 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2057 if (gravity == Gravity.TOP) {
2058 return top;
2059 } else if (gravity == Gravity.BOTTOM) {
2060 return top + viewht - layoutht;
2061 } else { // (gravity == Gravity.CENTER_VERTICAL)
2062 return top + (viewht - layoutht) / 2;
2063 }
2064 }
2065
2066 /**
2067 * Returns the extended bottom padding of the view, including both the
2068 * bottom Drawable if any and any extra space to keep more than maxLines
2069 * of text from showing. It is only valid to call this after measuring.
2070 */
2071 public int getExtendedPaddingBottom() {
2072 if (mMaxMode != LINES) {
2073 return getCompoundPaddingBottom();
2074 }
2075
Raph Levien463cf1a2014-09-02 14:22:23 -07002076 if (mLayout == null) {
2077 assumeLayout();
2078 }
2079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002080 if (mLayout.getLineCount() <= mMaximum) {
2081 return getCompoundPaddingBottom();
2082 }
2083
2084 int top = getCompoundPaddingTop();
2085 int bottom = getCompoundPaddingBottom();
2086 int viewht = getHeight() - top - bottom;
2087 int layoutht = mLayout.getLineTop(mMaximum);
2088
2089 if (layoutht >= viewht) {
2090 return bottom;
2091 }
2092
2093 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2094 if (gravity == Gravity.TOP) {
2095 return bottom + viewht - layoutht;
2096 } else if (gravity == Gravity.BOTTOM) {
2097 return bottom;
2098 } else { // (gravity == Gravity.CENTER_VERTICAL)
2099 return bottom + (viewht - layoutht) / 2;
2100 }
2101 }
2102
2103 /**
2104 * Returns the total left padding of the view, including the left
2105 * Drawable if any.
2106 */
2107 public int getTotalPaddingLeft() {
2108 return getCompoundPaddingLeft();
2109 }
2110
2111 /**
2112 * Returns the total right padding of the view, including the right
2113 * Drawable if any.
2114 */
2115 public int getTotalPaddingRight() {
2116 return getCompoundPaddingRight();
2117 }
2118
2119 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002120 * Returns the total start padding of the view, including the start
2121 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002122 */
2123 public int getTotalPaddingStart() {
2124 return getCompoundPaddingStart();
2125 }
2126
2127 /**
2128 * Returns the total end padding of the view, including the end
2129 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002130 */
2131 public int getTotalPaddingEnd() {
2132 return getCompoundPaddingEnd();
2133 }
2134
2135 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002136 * Returns the total top padding of the view, including the top
2137 * Drawable if any, the extra space to keep more than maxLines
2138 * from showing, and the vertical offset for gravity, if any.
2139 */
2140 public int getTotalPaddingTop() {
2141 return getExtendedPaddingTop() + getVerticalOffset(true);
2142 }
2143
2144 /**
2145 * Returns the total bottom padding of the view, including the bottom
2146 * Drawable if any, the extra space to keep more than maxLines
2147 * from showing, and the vertical offset for gravity, if any.
2148 */
2149 public int getTotalPaddingBottom() {
2150 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2151 }
2152
2153 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002154 * Sets the Drawables (if any) to appear to the left of, above, to the
2155 * right of, and below the text. Use {@code null} if you do not want a
2156 * Drawable there. The Drawables must already have had
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002157 * {@link Drawable#setBounds} called.
Alan Viverette97f84ee2014-09-09 16:55:56 -07002158 * <p>
2159 * Calling this method will overwrite any Drawables previously set using
2160 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002161 *
2162 * @attr ref android.R.styleable#TextView_drawableLeft
2163 * @attr ref android.R.styleable#TextView_drawableTop
2164 * @attr ref android.R.styleable#TextView_drawableRight
2165 * @attr ref android.R.styleable#TextView_drawableBottom
2166 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002167 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2168 @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002169 Drawables dr = mDrawables;
2170
Alan Viverette97f84ee2014-09-09 16:55:56 -07002171 // We're switching to absolute, discard relative.
2172 if (dr != null) {
2173 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2174 dr.mDrawableStart = null;
2175 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2176 dr.mDrawableEnd = null;
2177 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2178 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002180
Alan Viverette97f84ee2014-09-09 16:55:56 -07002181 final boolean drawables = left != null || top != null || right != null || bottom != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002182 if (!drawables) {
2183 // Clearing drawables... can we free the data structure?
2184 if (dr != null) {
Alan Viverette1c9d20b2016-02-03 10:45:55 -05002185 if (!dr.hasMetadata()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002186 mDrawables = null;
2187 } else {
2188 // We need to retain the last set padding, so just clear
2189 // out all of the fields in the existing structure.
Alan Viveretteb97d6982015-01-07 16:16:20 -08002190 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2191 if (dr.mShowing[i] != null) {
2192 dr.mShowing[i].setCallback(null);
2193 }
2194 dr.mShowing[i] = null;
2195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002196 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2197 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2198 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2199 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2200 }
2201 }
2202 } else {
2203 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002204 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002205 }
2206
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002207 mDrawables.mOverride = false;
2208
Alan Viveretteb97d6982015-01-07 16:16:20 -08002209 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2210 dr.mShowing[Drawables.LEFT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002211 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002212 dr.mShowing[Drawables.LEFT] = left;
Romain Guy8e618e52010-03-08 12:18:20 -08002213
Alan Viveretteb97d6982015-01-07 16:16:20 -08002214 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2215 dr.mShowing[Drawables.TOP].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002216 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002217 dr.mShowing[Drawables.TOP] = top;
Romain Guy8e618e52010-03-08 12:18:20 -08002218
Alan Viveretteb97d6982015-01-07 16:16:20 -08002219 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2220 dr.mShowing[Drawables.RIGHT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002221 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002222 dr.mShowing[Drawables.RIGHT] = right;
Romain Guy8e618e52010-03-08 12:18:20 -08002223
Alan Viveretteb97d6982015-01-07 16:16:20 -08002224 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2225 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002226 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002227 dr.mShowing[Drawables.BOTTOM] = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228
2229 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07002230 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002231
2232 state = getDrawableState();
2233
2234 if (left != null) {
2235 left.setState(state);
2236 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002237 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002238 dr.mDrawableSizeLeft = compoundRect.width();
2239 dr.mDrawableHeightLeft = compoundRect.height();
2240 } else {
2241 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2242 }
2243
2244 if (right != null) {
2245 right.setState(state);
2246 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002247 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002248 dr.mDrawableSizeRight = compoundRect.width();
2249 dr.mDrawableHeightRight = compoundRect.height();
2250 } else {
2251 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2252 }
2253
2254 if (top != null) {
2255 top.setState(state);
2256 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002257 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002258 dr.mDrawableSizeTop = compoundRect.height();
2259 dr.mDrawableWidthTop = compoundRect.width();
2260 } else {
2261 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2262 }
2263
2264 if (bottom != null) {
2265 bottom.setState(state);
2266 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002267 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002268 dr.mDrawableSizeBottom = compoundRect.height();
2269 dr.mDrawableWidthBottom = compoundRect.width();
2270 } else {
2271 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2272 }
2273 }
2274
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002275 // Save initial left/right drawables
2276 if (dr != null) {
2277 dr.mDrawableLeftInitial = left;
2278 dr.mDrawableRightInitial = right;
2279 }
2280
Fabrice Di Meglio3f5a90b2013-06-24 19:22:25 -07002281 resetResolvedDrawables();
2282 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08002283 applyCompoundDrawableTint();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002284 invalidate();
2285 requestLayout();
2286 }
2287
2288 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002289 * Sets the Drawables (if any) to appear to the left of, above, to the
2290 * right of, and below the text. Use 0 if you do not want a Drawable there.
2291 * The Drawables' bounds will be set to their intrinsic bounds.
2292 * <p>
2293 * Calling this method will overwrite any Drawables previously set using
2294 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002295 *
2296 * @param left Resource identifier of the left Drawable.
2297 * @param top Resource identifier of the top Drawable.
2298 * @param right Resource identifier of the right Drawable.
2299 * @param bottom Resource identifier of the bottom Drawable.
2300 *
2301 * @attr ref android.R.styleable#TextView_drawableLeft
2302 * @attr ref android.R.styleable#TextView_drawableTop
2303 * @attr ref android.R.styleable#TextView_drawableRight
2304 * @attr ref android.R.styleable#TextView_drawableBottom
2305 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002306 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002307 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2308 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002309 final Context context = getContext();
2310 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2311 top != 0 ? context.getDrawable(top) : null,
2312 right != 0 ? context.getDrawable(right) : null,
2313 bottom != 0 ? context.getDrawable(bottom) : null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002314 }
2315
2316 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002317 * Sets the Drawables (if any) to appear to the left of, above, to the
2318 * right of, and below the text. Use {@code null} if you do not want a
2319 * Drawable there. The Drawables' bounds will be set to their intrinsic
2320 * bounds.
2321 * <p>
2322 * Calling this method will overwrite any Drawables previously set using
2323 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002324 *
2325 * @attr ref android.R.styleable#TextView_drawableLeft
2326 * @attr ref android.R.styleable#TextView_drawableTop
2327 * @attr ref android.R.styleable#TextView_drawableRight
2328 * @attr ref android.R.styleable#TextView_drawableBottom
2329 */
Dan Sandler912282e2015-07-28 22:49:30 -04002330 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002331 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2332 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002333
2334 if (left != null) {
2335 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2336 }
2337 if (right != null) {
2338 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2339 }
2340 if (top != null) {
2341 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2342 }
2343 if (bottom != null) {
2344 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2345 }
2346 setCompoundDrawables(left, top, right, bottom);
2347 }
2348
2349 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002350 * Sets the Drawables (if any) to appear to the start of, above, to the end
2351 * of, and below the text. Use {@code null} if you do not want a Drawable
2352 * there. The Drawables must already have had {@link Drawable#setBounds}
2353 * called.
2354 * <p>
2355 * Calling this method will overwrite any Drawables previously set using
2356 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002357 *
2358 * @attr ref android.R.styleable#TextView_drawableStart
2359 * @attr ref android.R.styleable#TextView_drawableTop
2360 * @attr ref android.R.styleable#TextView_drawableEnd
2361 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002362 */
Dan Sandler912282e2015-07-28 22:49:30 -04002363 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002364 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2365 @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002366 Drawables dr = mDrawables;
2367
Alan Viverette97f84ee2014-09-09 16:55:56 -07002368 // We're switching to relative, discard absolute.
2369 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002370 if (dr.mShowing[Drawables.LEFT] != null) {
2371 dr.mShowing[Drawables.LEFT].setCallback(null);
2372 }
2373 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2374 if (dr.mShowing[Drawables.RIGHT] != null) {
2375 dr.mShowing[Drawables.RIGHT].setCallback(null);
2376 }
2377 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
Alan Viverette97f84ee2014-09-09 16:55:56 -07002378 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2379 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2380 }
2381
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002382 final boolean drawables = start != null || top != null
2383 || end != null || bottom != null;
2384
2385 if (!drawables) {
2386 // Clearing drawables... can we free the data structure?
2387 if (dr != null) {
Alan Viverette1c9d20b2016-02-03 10:45:55 -05002388 if (!dr.hasMetadata()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002389 mDrawables = null;
2390 } else {
2391 // We need to retain the last set padding, so just clear
2392 // out all of the fields in the existing structure.
2393 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2394 dr.mDrawableStart = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002395 if (dr.mShowing[Drawables.TOP] != null) {
2396 dr.mShowing[Drawables.TOP].setCallback(null);
2397 }
2398 dr.mShowing[Drawables.TOP] = null;
2399 if (dr.mDrawableEnd != null) {
2400 dr.mDrawableEnd.setCallback(null);
2401 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002402 dr.mDrawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002403 if (dr.mShowing[Drawables.BOTTOM] != null) {
2404 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2405 }
2406 dr.mShowing[Drawables.BOTTOM] = null;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002407 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2408 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2409 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2410 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2411 }
2412 }
2413 } else {
2414 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002415 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002416 }
2417
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002418 mDrawables.mOverride = true;
2419
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002420 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2421 dr.mDrawableStart.setCallback(null);
2422 }
2423 dr.mDrawableStart = start;
2424
Alan Viveretteb97d6982015-01-07 16:16:20 -08002425 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2426 dr.mShowing[Drawables.TOP].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002427 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002428 dr.mShowing[Drawables.TOP] = top;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002429
2430 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2431 dr.mDrawableEnd.setCallback(null);
2432 }
2433 dr.mDrawableEnd = end;
2434
Alan Viveretteb97d6982015-01-07 16:16:20 -08002435 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2436 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002437 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002438 dr.mShowing[Drawables.BOTTOM] = bottom;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002439
2440 final Rect compoundRect = dr.mCompoundRect;
2441 int[] state;
2442
2443 state = getDrawableState();
2444
2445 if (start != null) {
2446 start.setState(state);
2447 start.copyBounds(compoundRect);
2448 start.setCallback(this);
2449 dr.mDrawableSizeStart = compoundRect.width();
2450 dr.mDrawableHeightStart = compoundRect.height();
2451 } else {
2452 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2453 }
2454
2455 if (end != null) {
2456 end.setState(state);
2457 end.copyBounds(compoundRect);
2458 end.setCallback(this);
2459 dr.mDrawableSizeEnd = compoundRect.width();
2460 dr.mDrawableHeightEnd = compoundRect.height();
2461 } else {
2462 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2463 }
2464
2465 if (top != null) {
2466 top.setState(state);
2467 top.copyBounds(compoundRect);
2468 top.setCallback(this);
2469 dr.mDrawableSizeTop = compoundRect.height();
2470 dr.mDrawableWidthTop = compoundRect.width();
2471 } else {
2472 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2473 }
2474
2475 if (bottom != null) {
2476 bottom.setState(state);
2477 bottom.copyBounds(compoundRect);
2478 bottom.setCallback(this);
2479 dr.mDrawableSizeBottom = compoundRect.height();
2480 dr.mDrawableWidthBottom = compoundRect.width();
2481 } else {
2482 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2483 }
2484 }
2485
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002486 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002487 resolveDrawables();
2488 invalidate();
2489 requestLayout();
2490 }
2491
2492 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002493 * Sets the Drawables (if any) to appear to the start of, above, to the end
2494 * of, and below the text. Use 0 if you do not want a Drawable there. The
2495 * Drawables' bounds will be set to their intrinsic bounds.
2496 * <p>
2497 * Calling this method will overwrite any Drawables previously set using
2498 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002499 *
2500 * @param start Resource identifier of the start Drawable.
2501 * @param top Resource identifier of the top Drawable.
2502 * @param end Resource identifier of the end Drawable.
2503 * @param bottom Resource identifier of the bottom Drawable.
2504 *
2505 * @attr ref android.R.styleable#TextView_drawableStart
2506 * @attr ref android.R.styleable#TextView_drawableTop
2507 * @attr ref android.R.styleable#TextView_drawableEnd
2508 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002509 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002510 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002511 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2512 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002513 final Context context = getContext();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002514 setCompoundDrawablesRelativeWithIntrinsicBounds(
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002515 start != 0 ? context.getDrawable(start) : null,
2516 top != 0 ? context.getDrawable(top) : null,
2517 end != 0 ? context.getDrawable(end) : null,
2518 bottom != 0 ? context.getDrawable(bottom) : null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002519 }
2520
2521 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002522 * Sets the Drawables (if any) to appear to the start of, above, to the end
2523 * of, and below the text. Use {@code null} if you do not want a Drawable
2524 * there. The Drawables' bounds will be set to their intrinsic bounds.
2525 * <p>
2526 * Calling this method will overwrite any Drawables previously set using
2527 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002528 *
2529 * @attr ref android.R.styleable#TextView_drawableStart
2530 * @attr ref android.R.styleable#TextView_drawableTop
2531 * @attr ref android.R.styleable#TextView_drawableEnd
2532 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002533 */
Dan Sandler912282e2015-07-28 22:49:30 -04002534 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002535 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2536 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002537
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002538 if (start != null) {
2539 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2540 }
2541 if (end != null) {
2542 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2543 }
2544 if (top != null) {
2545 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2546 }
2547 if (bottom != null) {
2548 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2549 }
2550 setCompoundDrawablesRelative(start, top, end, bottom);
2551 }
2552
2553 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002554 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002555 *
2556 * @attr ref android.R.styleable#TextView_drawableLeft
2557 * @attr ref android.R.styleable#TextView_drawableTop
2558 * @attr ref android.R.styleable#TextView_drawableRight
2559 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002560 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002561 @NonNull
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002562 public Drawable[] getCompoundDrawables() {
2563 final Drawables dr = mDrawables;
2564 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002565 return dr.mShowing.clone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002566 } else {
2567 return new Drawable[] { null, null, null, null };
2568 }
2569 }
2570
2571 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002572 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002573 *
2574 * @attr ref android.R.styleable#TextView_drawableStart
2575 * @attr ref android.R.styleable#TextView_drawableTop
2576 * @attr ref android.R.styleable#TextView_drawableEnd
2577 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002578 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002579 @NonNull
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002580 public Drawable[] getCompoundDrawablesRelative() {
2581 final Drawables dr = mDrawables;
2582 if (dr != null) {
2583 return new Drawable[] {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002584 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2585 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002586 };
2587 } else {
2588 return new Drawable[] { null, null, null, null };
2589 }
2590 }
2591
2592 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002593 * Sets the size of the padding between the compound drawables and
2594 * the text.
2595 *
2596 * @attr ref android.R.styleable#TextView_drawablePadding
2597 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002598 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002599 public void setCompoundDrawablePadding(int pad) {
2600 Drawables dr = mDrawables;
2601 if (pad == 0) {
2602 if (dr != null) {
2603 dr.mDrawablePadding = pad;
2604 }
2605 } else {
2606 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002607 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002608 }
2609 dr.mDrawablePadding = pad;
2610 }
2611
2612 invalidate();
2613 requestLayout();
2614 }
2615
2616 /**
2617 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002618 *
2619 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002620 */
2621 public int getCompoundDrawablePadding() {
2622 final Drawables dr = mDrawables;
2623 return dr != null ? dr.mDrawablePadding : 0;
2624 }
2625
Alan Viveretteb97d6982015-01-07 16:16:20 -08002626 /**
2627 * Applies a tint to the compound drawables. Does not modify the
2628 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2629 * <p>
2630 * Subsequent calls to
2631 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2632 * and related methods will automatically mutate the drawables and apply
2633 * the specified tint and tint mode using
2634 * {@link Drawable#setTintList(ColorStateList)}.
2635 *
2636 * @param tint the tint to apply, may be {@code null} to clear tint
2637 *
2638 * @attr ref android.R.styleable#TextView_drawableTint
2639 * @see #getCompoundDrawableTintList()
2640 * @see Drawable#setTintList(ColorStateList)
2641 */
2642 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2643 if (mDrawables == null) {
2644 mDrawables = new Drawables(getContext());
2645 }
2646 mDrawables.mTintList = tint;
2647 mDrawables.mHasTint = true;
2648
2649 applyCompoundDrawableTint();
2650 }
2651
2652 /**
2653 * @return the tint applied to the compound drawables
2654 * @attr ref android.R.styleable#TextView_drawableTint
2655 * @see #setCompoundDrawableTintList(ColorStateList)
2656 */
2657 public ColorStateList getCompoundDrawableTintList() {
2658 return mDrawables != null ? mDrawables.mTintList : null;
2659 }
2660
2661 /**
2662 * Specifies the blending mode used to apply the tint specified by
2663 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2664 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2665 *
2666 * @param tintMode the blending mode used to apply the tint, may be
2667 * {@code null} to clear tint
2668 * @attr ref android.R.styleable#TextView_drawableTintMode
2669 * @see #setCompoundDrawableTintList(ColorStateList)
2670 * @see Drawable#setTintMode(PorterDuff.Mode)
2671 */
2672 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2673 if (mDrawables == null) {
2674 mDrawables = new Drawables(getContext());
2675 }
2676 mDrawables.mTintMode = tintMode;
2677 mDrawables.mHasTintMode = true;
2678
2679 applyCompoundDrawableTint();
2680 }
2681
2682 /**
2683 * Returns the blending mode used to apply the tint to the compound
2684 * drawables, if specified.
2685 *
2686 * @return the blending mode used to apply the tint to the compound
2687 * drawables
2688 * @attr ref android.R.styleable#TextView_drawableTintMode
2689 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2690 */
2691 public PorterDuff.Mode getCompoundDrawableTintMode() {
2692 return mDrawables != null ? mDrawables.mTintMode : null;
2693 }
2694
2695 private void applyCompoundDrawableTint() {
2696 if (mDrawables == null) {
2697 return;
2698 }
2699
2700 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2701 final ColorStateList tintList = mDrawables.mTintList;
2702 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2703 final boolean hasTint = mDrawables.mHasTint;
2704 final boolean hasTintMode = mDrawables.mHasTintMode;
2705 final int[] state = getDrawableState();
2706
2707 for (Drawable dr : mDrawables.mShowing) {
2708 if (dr == null) {
2709 continue;
2710 }
2711
2712 if (dr == mDrawables.mDrawableError) {
2713 // From a developer's perspective, the error drawable isn't
2714 // a compound drawable. Don't apply the generic compound
2715 // drawable tint to it.
2716 continue;
2717 }
2718
2719 dr.mutate();
2720
2721 if (hasTint) {
2722 dr.setTintList(tintList);
2723 }
2724
2725 if (hasTintMode) {
2726 dr.setTintMode(tintMode);
2727 }
2728
2729 // The drawable (or one of its children) may not have been
2730 // stateful before applying the tint, so let's try again.
2731 if (dr.isStateful()) {
2732 dr.setState(state);
2733 }
2734 }
2735 }
2736 }
2737
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002738 @Override
2739 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002740 if (left != mPaddingLeft ||
2741 right != mPaddingRight ||
2742 top != mPaddingTop ||
2743 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002744 nullLayouts();
2745 }
2746
2747 // the super call will requestLayout()
2748 super.setPadding(left, top, right, bottom);
2749 invalidate();
2750 }
2751
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002752 @Override
2753 public void setPaddingRelative(int start, int top, int end, int bottom) {
2754 if (start != getPaddingStart() ||
2755 end != getPaddingEnd() ||
2756 top != mPaddingTop ||
2757 bottom != mPaddingBottom) {
2758 nullLayouts();
2759 }
2760
2761 // the super call will requestLayout()
2762 super.setPaddingRelative(start, top, end, bottom);
2763 invalidate();
2764 }
2765
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002766 /**
2767 * Gets the autolink mask of the text. See {@link
2768 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2769 * possible values.
2770 *
2771 * @attr ref android.R.styleable#TextView_autoLink
2772 */
2773 public final int getAutoLinkMask() {
2774 return mAutoLinkMask;
2775 }
2776
2777 /**
Alan Viverette38082272015-03-16 09:41:30 -07002778 * Sets the text appearance from the specified style resource.
2779 * <p>
2780 * Use a framework-defined {@code TextAppearance} style like
2781 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2782 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2783 * set of attributes that can be used in a custom style.
2784 *
2785 * @param resId the resource identifier of the style to apply
2786 * @attr ref android.R.styleable#TextView_textAppearance
2787 */
2788 @SuppressWarnings("deprecation")
2789 public void setTextAppearance(@StyleRes int resId) {
2790 setTextAppearance(mContext, resId);
2791 }
2792
2793 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002794 * Sets the text color, size, style, hint color, and highlight color
2795 * from the specified TextAppearance resource.
Alan Viverette38082272015-03-16 09:41:30 -07002796 *
2797 * @deprecated Use {@link #setTextAppearance(int)} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002798 */
Alan Viverette38082272015-03-16 09:41:30 -07002799 @Deprecated
2800 public void setTextAppearance(Context context, @StyleRes int resId) {
2801 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002802
Alan Viverette38082272015-03-16 09:41:30 -07002803 final int textColorHighlight = ta.getColor(
2804 R.styleable.TextAppearance_textColorHighlight, 0);
2805 if (textColorHighlight != 0) {
2806 setHighlightColor(textColorHighlight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002807 }
2808
Alan Viverette38082272015-03-16 09:41:30 -07002809 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2810 if (textColor != null) {
2811 setTextColor(textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002812 }
2813
Alan Viverette38082272015-03-16 09:41:30 -07002814 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2815 if (textSize != 0) {
2816 setRawTextSize(textSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002817 }
2818
Alan Viverette38082272015-03-16 09:41:30 -07002819 final ColorStateList textColorHint = ta.getColorStateList(
2820 R.styleable.TextAppearance_textColorHint);
2821 if (textColorHint != null) {
2822 setHintTextColor(textColorHint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002823 }
2824
Alan Viverette38082272015-03-16 09:41:30 -07002825 final ColorStateList textColorLink = ta.getColorStateList(
2826 R.styleable.TextAppearance_textColorLink);
2827 if (textColorLink != null) {
2828 setLinkTextColor(textColorLink);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002829 }
2830
Alan Viverette38082272015-03-16 09:41:30 -07002831 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2832 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2833 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2834 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002835
Alan Viverette38082272015-03-16 09:41:30 -07002836 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2837 if (shadowColor != 0) {
2838 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2839 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2840 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2841 setShadowLayer(r, dx, dy, shadowColor);
Adam Powellac91df82013-02-14 13:48:47 -08002842 }
2843
Alan Viverette38082272015-03-16 09:41:30 -07002844 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
Adam Powell7f8f79a2011-07-07 18:35:54 -07002845 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2846 }
2847
Alan Viverette38082272015-03-16 09:41:30 -07002848 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2849 setElegantTextHeight(ta.getBoolean(
2850 R.styleable.TextAppearance_elegantTextHeight, false));
Raph Levien53c00772014-04-14 14:11:02 -07002851 }
2852
Alan Viverette38082272015-03-16 09:41:30 -07002853 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2854 setLetterSpacing(ta.getFloat(
2855 R.styleable.TextAppearance_letterSpacing, 0));
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002856 }
2857
Alan Viverette38082272015-03-16 09:41:30 -07002858 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2859 setFontFeatureSettings(ta.getString(
2860 R.styleable.TextAppearance_fontFeatureSettings));
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04002861 }
2862
Alan Viverette38082272015-03-16 09:41:30 -07002863 ta.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002864 }
2865
2866 /**
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002867 * Get the default primary {@link Locale} of the text in this TextView. This will always be
2868 * the first member of {@link #getTextLocales()}.
2869 * @return the default primary {@link Locale} of the text in this TextView.
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002870 */
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002871 @NonNull
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002872 public Locale getTextLocale() {
2873 return mTextPaint.getTextLocale();
2874 }
2875
2876 /**
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002877 * Get the default {@link LocaleList} of the text in this TextView.
2878 * @return the default {@link LocaleList} of the text in this TextView.
2879 */
2880 @NonNull @Size(min=1)
2881 public LocaleList getTextLocales() {
2882 return mTextPaint.getTextLocales();
2883 }
2884
2885 /**
2886 * Set the default {@link LocaleList} of the text in this TextView to a one-member list
2887 * containing just the given value.
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002888 *
2889 * @param locale the {@link Locale} for drawing text, must not be null.
2890 *
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002891 * @see #setTextLocales
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002892 */
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002893 public void setTextLocale(@NonNull Locale locale) {
2894 mLocalesChanged = true;
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002895 mTextPaint.setTextLocale(locale);
2896 }
2897
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002898 /**
2899 * Set the default {@link LocaleList} of the text in this TextView to the given value.
2900 *
2901 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
2902 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
2903 * other aspects of text display, including line breaking.
2904 *
2905 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
2906 *
2907 * @see Paint#setTextLocales
2908 */
2909 public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
2910 mLocalesChanged = true;
2911 mTextPaint.setTextLocales(locales);
2912 }
2913
Raph Levien2e3aa442015-01-14 16:12:53 -08002914 @Override
2915 protected void onConfigurationChanged(Configuration newConfig) {
2916 super.onConfigurationChanged(newConfig);
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002917 if (!mLocalesChanged) {
2918 mTextPaint.setTextLocales(LocaleList.getDefault());
Raph Levien2e3aa442015-01-14 16:12:53 -08002919 }
2920 }
2921
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002922 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002923 * @return the size (in pixels) of the default text size in this TextView.
2924 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002925 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002926 public float getTextSize() {
2927 return mTextPaint.getTextSize();
2928 }
2929
2930 /**
Jon Mirandaa25dc4282014-07-01 14:35:07 -07002931 * @return the size (in scaled pixels) of thee default text size in this TextView.
2932 * @hide
2933 */
2934 @ViewDebug.ExportedProperty(category = "text")
2935 public float getScaledTextSize() {
2936 return mTextPaint.getTextSize() / mTextPaint.density;
2937 }
2938
2939 /** @hide */
2940 @ViewDebug.ExportedProperty(category = "text", mapping = {
2941 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2942 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2943 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2944 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2945 })
2946 public int getTypefaceStyle() {
Siva Velusamy94a6d152015-05-05 15:07:00 -07002947 Typeface typeface = mTextPaint.getTypeface();
2948 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
Jon Mirandaa25dc4282014-07-01 14:35:07 -07002949 }
2950
2951 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002952 * Set the default text size to the given value, interpreted as "scaled
2953 * pixel" units. This size is adjusted based on the current density and
2954 * user font size preference.
2955 *
2956 * @param size The scaled pixel size.
2957 *
2958 * @attr ref android.R.styleable#TextView_textSize
2959 */
2960 @android.view.RemotableViewMethod
2961 public void setTextSize(float size) {
2962 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2963 }
2964
2965 /**
2966 * Set the default text size to a given unit and value. See {@link
2967 * TypedValue} for the possible dimension units.
2968 *
2969 * @param unit The desired dimension unit.
2970 * @param size The desired size in the given units.
2971 *
2972 * @attr ref android.R.styleable#TextView_textSize
2973 */
2974 public void setTextSize(int unit, float size) {
2975 Context c = getContext();
2976 Resources r;
2977
2978 if (c == null)
2979 r = Resources.getSystem();
2980 else
2981 r = c.getResources();
2982
2983 setRawTextSize(TypedValue.applyDimension(
Alan Viverettecebc6ba2014-06-13 15:52:13 -07002984 unit, size, r.getDisplayMetrics()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002985 }
2986
2987 private void setRawTextSize(float size) {
2988 if (size != mTextPaint.getTextSize()) {
2989 mTextPaint.setTextSize(size);
2990
2991 if (mLayout != null) {
2992 nullLayouts();
2993 requestLayout();
2994 invalidate();
2995 }
2996 }
2997 }
2998
2999 /**
3000 * @return the extent by which text is currently being stretched
3001 * horizontally. This will usually be 1.
3002 */
3003 public float getTextScaleX() {
3004 return mTextPaint.getTextScaleX();
3005 }
3006
3007 /**
3008 * Sets the extent by which text should be stretched horizontally.
3009 *
3010 * @attr ref android.R.styleable#TextView_textScaleX
3011 */
3012 @android.view.RemotableViewMethod
3013 public void setTextScaleX(float size) {
3014 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07003015 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003016 mTextPaint.setTextScaleX(size);
3017
3018 if (mLayout != null) {
3019 nullLayouts();
3020 requestLayout();
3021 invalidate();
3022 }
3023 }
3024 }
3025
3026 /**
3027 * Sets the typeface and style in which the text should be displayed.
3028 * Note that not all Typeface families actually have bold and italic
3029 * variants, so you may need to use
3030 * {@link #setTypeface(Typeface, int)} to get the appearance
3031 * that you actually want.
3032 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003033 * @see #getTypeface()
3034 *
Raph Leviend570e892012-05-09 11:45:34 -07003035 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003036 * @attr ref android.R.styleable#TextView_typeface
3037 * @attr ref android.R.styleable#TextView_textStyle
3038 */
3039 public void setTypeface(Typeface tf) {
3040 if (mTextPaint.getTypeface() != tf) {
3041 mTextPaint.setTypeface(tf);
3042
3043 if (mLayout != null) {
3044 nullLayouts();
3045 requestLayout();
3046 invalidate();
3047 }
3048 }
3049 }
3050
3051 /**
3052 * @return the current typeface and style in which the text is being
3053 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07003054 *
3055 * @see #setTypeface(Typeface)
3056 *
Raph Leviend570e892012-05-09 11:45:34 -07003057 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07003058 * @attr ref android.R.styleable#TextView_typeface
3059 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003060 */
3061 public Typeface getTypeface() {
3062 return mTextPaint.getTypeface();
3063 }
3064
3065 /**
Raph Levien53c00772014-04-14 14:11:02 -07003066 * Set the TextView's elegant height metrics flag. This setting selects font
3067 * variants that have not been compacted to fit Latin-based vertical
3068 * metrics, and also increases top and bottom bounds to provide more space.
3069 *
3070 * @param elegant set the paint's elegant metrics flag.
Raph Leviene1c4a0d2014-06-05 15:26:59 -07003071 *
3072 * @attr ref android.R.styleable#TextView_elegantTextHeight
Raph Levien53c00772014-04-14 14:11:02 -07003073 */
3074 public void setElegantTextHeight(boolean elegant) {
3075 mTextPaint.setElegantTextHeight(elegant);
3076 }
3077
3078 /**
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003079 * @return the extent by which text is currently being letter-spaced.
3080 * This will normally be 0.
3081 *
3082 * @see #setLetterSpacing(float)
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003083 * @see Paint#setLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003084 */
3085 public float getLetterSpacing() {
3086 return mTextPaint.getLetterSpacing();
3087 }
3088
3089 /**
3090 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3091 * for slight expansion will be around 0.05. Negative values tighten text.
3092 *
3093 * @see #getLetterSpacing()
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003094 * @see Paint#getLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003095 *
3096 * @attr ref android.R.styleable#TextView_letterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003097 */
3098 @android.view.RemotableViewMethod
3099 public void setLetterSpacing(float letterSpacing) {
3100 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3101 mTextPaint.setLetterSpacing(letterSpacing);
3102
3103 if (mLayout != null) {
3104 nullLayouts();
3105 requestLayout();
3106 invalidate();
3107 }
3108 }
3109 }
3110
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003111 /**
3112 * @return the currently set font feature settings. Default is null.
3113 *
3114 * @see #setFontFeatureSettings(String)
3115 * @see Paint#setFontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003116 */
Raph Leviene272a262014-08-07 16:07:51 -07003117 @Nullable
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003118 public String getFontFeatureSettings() {
3119 return mTextPaint.getFontFeatureSettings();
3120 }
3121
3122 /**
Raph Levien39b4db72015-03-25 13:18:20 -07003123 * Sets the break strategy for breaking paragraphs into lines. The default value for
3124 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3125 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3126 * text "dancing" when being edited.
3127 *
3128 * @attr ref android.R.styleable#TextView_breakStrategy
3129 * @see #getBreakStrategy()
3130 */
3131 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3132 mBreakStrategy = breakStrategy;
3133 if (mLayout != null) {
3134 nullLayouts();
3135 requestLayout();
3136 invalidate();
3137 }
3138 }
3139
3140 /**
3141 * @return the currently set break strategy.
3142 *
3143 * @attr ref android.R.styleable#TextView_breakStrategy
3144 * @see #setBreakStrategy(int)
3145 */
3146 @Layout.BreakStrategy
3147 public int getBreakStrategy() {
3148 return mBreakStrategy;
3149 }
3150
3151 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07003152 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3153 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3154 *
3155 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3156 * @see #getHyphenationFrequency()
3157 */
3158 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3159 mHyphenationFrequency = hyphenationFrequency;
3160 if (mLayout != null) {
3161 nullLayouts();
3162 requestLayout();
3163 invalidate();
3164 }
3165 }
3166
3167 /**
3168 * @return the currently set hyphenation frequency.
3169 *
3170 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3171 * @see #setHyphenationFrequency(int)
3172 */
3173 @Layout.HyphenationFrequency
3174 public int getHyphenationFrequency() {
3175 return mHyphenationFrequency;
3176 }
3177
3178 /**
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003179 * Sets font feature settings. The format is the same as the CSS
3180 * font-feature-settings attribute:
3181 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3182 *
Raph Leviene272a262014-08-07 16:07:51 -07003183 * @param fontFeatureSettings font feature settings represented as CSS compatible string
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003184 * @see #getFontFeatureSettings()
3185 * @see Paint#getFontFeatureSettings
3186 *
3187 * @attr ref android.R.styleable#TextView_fontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003188 */
3189 @android.view.RemotableViewMethod
Raph Leviene272a262014-08-07 16:07:51 -07003190 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003191 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3192 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3193
3194 if (mLayout != null) {
3195 nullLayouts();
3196 requestLayout();
3197 invalidate();
3198 }
3199 }
3200 }
3201
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003202
3203 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003204 * Sets the text color for all the states (normal, selected,
3205 * focused) to be this color.
3206 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003207 * @see #setTextColor(ColorStateList)
3208 * @see #getTextColors()
3209 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003210 * @attr ref android.R.styleable#TextView_textColor
3211 */
3212 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003213 public void setTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003214 mTextColor = ColorStateList.valueOf(color);
3215 updateTextColors();
3216 }
3217
3218 /**
3219 * Sets the text color.
3220 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003221 * @see #setTextColor(int)
3222 * @see #getTextColors()
3223 * @see #setHintTextColor(ColorStateList)
3224 * @see #setLinkTextColor(ColorStateList)
3225 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003226 * @attr ref android.R.styleable#TextView_textColor
3227 */
3228 public void setTextColor(ColorStateList colors) {
3229 if (colors == null) {
3230 throw new NullPointerException();
3231 }
3232
3233 mTextColor = colors;
3234 updateTextColors();
3235 }
3236
3237 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003238 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003239 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003240 * @see #setTextColor(ColorStateList)
3241 * @see #setTextColor(int)
3242 *
3243 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003244 */
3245 public final ColorStateList getTextColors() {
3246 return mTextColor;
3247 }
3248
3249 /**
3250 * <p>Return the current color selected for normal text.</p>
3251 *
3252 * @return Returns the current text color.
3253 */
Tor Norbye80756e32015-03-02 09:39:27 -08003254 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003255 public final int getCurrentTextColor() {
3256 return mCurTextColor;
3257 }
3258
3259 /**
3260 * Sets the color used to display the selection highlight.
3261 *
3262 * @attr ref android.R.styleable#TextView_textColorHighlight
3263 */
3264 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003265 public void setHighlightColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003266 if (mHighlightColor != color) {
3267 mHighlightColor = color;
3268 invalidate();
3269 }
3270 }
3271
3272 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003273 * @return the color used to display the selection highlight
3274 *
3275 * @see #setHighlightColor(int)
3276 *
3277 * @attr ref android.R.styleable#TextView_textColorHighlight
3278 */
Tor Norbye80756e32015-03-02 09:39:27 -08003279 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003280 public int getHighlightColor() {
3281 return mHighlightColor;
3282 }
3283
3284 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003285 * Sets whether the soft input method will be made visible when this
3286 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003287 */
3288 @android.view.RemotableViewMethod
3289 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003290 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003291 mEditor.mShowSoftInputOnFocus = show;
3292 }
3293
3294 /**
3295 * Returns whether the soft input method will be made visible when this
3296 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003297 */
3298 public final boolean getShowSoftInputOnFocus() {
3299 // When there is no Editor, return default true value
3300 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3301 }
3302
3303 /**
Chris Craik5faf85b2014-08-20 17:16:05 -07003304 * Gives the text a shadow of the specified blur radius and color, the specified
3305 * distance from its drawn position.
3306 * <p>
3307 * The text shadow produced does not interact with the properties on view
3308 * that are responsible for real time shadows,
3309 * {@link View#getElevation() elevation} and
3310 * {@link View#getTranslationZ() translationZ}.
3311 *
3312 * @see Paint#setShadowLayer(float, float, float, int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003313 *
3314 * @attr ref android.R.styleable#TextView_shadowColor
3315 * @attr ref android.R.styleable#TextView_shadowDx
3316 * @attr ref android.R.styleable#TextView_shadowDy
3317 * @attr ref android.R.styleable#TextView_shadowRadius
3318 */
3319 public void setShadowLayer(float radius, float dx, float dy, int color) {
3320 mTextPaint.setShadowLayer(radius, dx, dy, color);
3321
3322 mShadowRadius = radius;
3323 mShadowDx = dx;
3324 mShadowDy = dy;
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003325 mShadowColor = color;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003326
Gilles Debunne33b7de852012-03-12 11:57:48 -07003327 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07003328 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003329 invalidate();
3330 }
3331
3332 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003333 * Gets the radius of the shadow layer.
3334 *
3335 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3336 *
3337 * @see #setShadowLayer(float, float, float, int)
3338 *
3339 * @attr ref android.R.styleable#TextView_shadowRadius
3340 */
3341 public float getShadowRadius() {
3342 return mShadowRadius;
3343 }
3344
3345 /**
3346 * @return the horizontal offset of the shadow layer
3347 *
3348 * @see #setShadowLayer(float, float, float, int)
3349 *
3350 * @attr ref android.R.styleable#TextView_shadowDx
3351 */
3352 public float getShadowDx() {
3353 return mShadowDx;
3354 }
3355
3356 /**
3357 * @return the vertical offset of the shadow layer
3358 *
3359 * @see #setShadowLayer(float, float, float, int)
3360 *
3361 * @attr ref android.R.styleable#TextView_shadowDy
3362 */
3363 public float getShadowDy() {
3364 return mShadowDy;
3365 }
3366
3367 /**
3368 * @return the color of the shadow layer
3369 *
3370 * @see #setShadowLayer(float, float, float, int)
3371 *
3372 * @attr ref android.R.styleable#TextView_shadowColor
3373 */
Tor Norbye80756e32015-03-02 09:39:27 -08003374 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003375 public int getShadowColor() {
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003376 return mShadowColor;
Gilles Debunnef03acef2012-04-30 19:26:19 -07003377 }
3378
3379 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003380 * @return the base paint used for the text. Please use this only to
3381 * consult the Paint's properties and not to change them.
3382 */
3383 public TextPaint getPaint() {
3384 return mTextPaint;
3385 }
3386
3387 /**
3388 * Sets the autolink mask of the text. See {@link
3389 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3390 * possible values.
3391 *
3392 * @attr ref android.R.styleable#TextView_autoLink
3393 */
3394 @android.view.RemotableViewMethod
3395 public final void setAutoLinkMask(int mask) {
3396 mAutoLinkMask = mask;
3397 }
3398
3399 /**
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08003400 * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
3401 * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
3402 * following is true:
3403 * <ul>
3404 * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
3405 * {@link #setText} or {@link #append}.
3406 * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
3407 * </ul>
3408 *
3409 * <p>This function does not have an immediate effect, movement method will be set only after a
3410 * call to {@link #setText} or {@link #append}. The default is true.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003411 *
3412 * @attr ref android.R.styleable#TextView_linksClickable
3413 */
3414 @android.view.RemotableViewMethod
3415 public final void setLinksClickable(boolean whether) {
3416 mLinksClickable = whether;
3417 }
3418
3419 /**
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08003420 * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
3421 * after {@link #setText} or {@link #append} is called.
3422 *
3423 * See {@link #setLinksClickable} for details.
3424 *
3425 * <p>The default is true.</p>
3426 *
3427 * @see #setLinksClickable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003428 *
3429 * @attr ref android.R.styleable#TextView_linksClickable
3430 */
3431 public final boolean getLinksClickable() {
3432 return mLinksClickable;
3433 }
3434
3435 /**
3436 * Returns the list of URLSpans attached to the text
3437 * (by {@link Linkify} or otherwise) if any. You can call
3438 * {@link URLSpan#getURL} on them to find where they link to
3439 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3440 * to find the region of the text they are attached to.
3441 */
3442 public URLSpan[] getUrls() {
3443 if (mText instanceof Spanned) {
3444 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3445 } else {
3446 return new URLSpan[0];
3447 }
3448 }
3449
3450 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003451 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3452 * TextView.
3453 *
3454 * @see #setHintTextColor(ColorStateList)
3455 * @see #getHintTextColors()
3456 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003457 *
3458 * @attr ref android.R.styleable#TextView_textColorHint
3459 */
3460 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003461 public final void setHintTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003462 mHintTextColor = ColorStateList.valueOf(color);
3463 updateTextColors();
3464 }
3465
3466 /**
3467 * Sets the color of the hint text.
3468 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003469 * @see #getHintTextColors()
3470 * @see #setHintTextColor(int)
3471 * @see #setTextColor(ColorStateList)
3472 * @see #setLinkTextColor(ColorStateList)
3473 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003474 * @attr ref android.R.styleable#TextView_textColorHint
3475 */
3476 public final void setHintTextColor(ColorStateList colors) {
3477 mHintTextColor = colors;
3478 updateTextColors();
3479 }
3480
3481 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003482 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003483 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003484 * @see #setHintTextColor(ColorStateList)
3485 * @see #setHintTextColor(int)
3486 * @see #setTextColor(ColorStateList)
3487 * @see #setLinkTextColor(ColorStateList)
3488 *
3489 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003490 */
3491 public final ColorStateList getHintTextColors() {
3492 return mHintTextColor;
3493 }
3494
3495 /**
3496 * <p>Return the current color selected to paint the hint text.</p>
3497 *
3498 * @return Returns the current hint text color.
3499 */
Tor Norbye80756e32015-03-02 09:39:27 -08003500 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003501 public final int getCurrentHintTextColor() {
3502 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3503 }
3504
3505 /**
3506 * Sets the color of links in the text.
3507 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003508 * @see #setLinkTextColor(ColorStateList)
3509 * @see #getLinkTextColors()
3510 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003511 * @attr ref android.R.styleable#TextView_textColorLink
3512 */
3513 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003514 public final void setLinkTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003515 mLinkTextColor = ColorStateList.valueOf(color);
3516 updateTextColors();
3517 }
3518
3519 /**
3520 * Sets the color of links in the text.
3521 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003522 * @see #setLinkTextColor(int)
3523 * @see #getLinkTextColors()
3524 * @see #setTextColor(ColorStateList)
3525 * @see #setHintTextColor(ColorStateList)
3526 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003527 * @attr ref android.R.styleable#TextView_textColorLink
3528 */
3529 public final void setLinkTextColor(ColorStateList colors) {
3530 mLinkTextColor = colors;
3531 updateTextColors();
3532 }
3533
3534 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003535 * @return the list of colors used to paint the links in the text, for the different states of
3536 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003537 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003538 * @see #setLinkTextColor(ColorStateList)
3539 * @see #setLinkTextColor(int)
3540 *
3541 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003542 */
3543 public final ColorStateList getLinkTextColors() {
3544 return mLinkTextColor;
3545 }
3546
3547 /**
3548 * Sets the horizontal alignment of the text and the
3549 * vertical gravity that will be used when there is extra space
3550 * in the TextView beyond what is required for the text itself.
3551 *
3552 * @see android.view.Gravity
3553 * @attr ref android.R.styleable#TextView_gravity
3554 */
3555 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003556 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07003557 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003558 }
3559 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3560 gravity |= Gravity.TOP;
3561 }
3562
3563 boolean newLayout = false;
3564
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003565 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3566 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003567 newLayout = true;
3568 }
3569
3570 if (gravity != mGravity) {
3571 invalidate();
3572 }
3573
3574 mGravity = gravity;
3575
3576 if (mLayout != null && newLayout) {
3577 // XXX this is heavy-handed because no actual content changes.
3578 int want = mLayout.getWidth();
3579 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3580
3581 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3582 mRight - mLeft - getCompoundPaddingLeft() -
3583 getCompoundPaddingRight(), true);
3584 }
3585 }
3586
3587 /**
3588 * Returns the horizontal and vertical alignment of this TextView.
3589 *
3590 * @see android.view.Gravity
3591 * @attr ref android.R.styleable#TextView_gravity
3592 */
3593 public int getGravity() {
3594 return mGravity;
3595 }
3596
3597 /**
3598 * @return the flags on the Paint being used to display the text.
3599 * @see Paint#getFlags
3600 */
3601 public int getPaintFlags() {
3602 return mTextPaint.getFlags();
3603 }
3604
3605 /**
3606 * Sets flags on the Paint being used to display the text and
3607 * reflows the text if they are different from the old flags.
3608 * @see Paint#setFlags
3609 */
3610 @android.view.RemotableViewMethod
3611 public void setPaintFlags(int flags) {
3612 if (mTextPaint.getFlags() != flags) {
3613 mTextPaint.setFlags(flags);
3614
3615 if (mLayout != null) {
3616 nullLayouts();
3617 requestLayout();
3618 invalidate();
3619 }
3620 }
3621 }
3622
3623 /**
3624 * Sets whether the text should be allowed to be wider than the
3625 * View is. If false, it will be wrapped to the width of the View.
3626 *
3627 * @attr ref android.R.styleable#TextView_scrollHorizontally
3628 */
3629 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003630 if (mHorizontallyScrolling != whether) {
3631 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003632
Gilles Debunne22378292011-08-12 10:38:52 -07003633 if (mLayout != null) {
3634 nullLayouts();
3635 requestLayout();
3636 invalidate();
3637 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003638 }
3639 }
3640
3641 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003642 * Returns whether the text is allowed to be wider than the View is.
3643 * If false, the text will be wrapped to the width of the View.
3644 *
3645 * @attr ref android.R.styleable#TextView_scrollHorizontally
3646 * @hide
3647 */
3648 public boolean getHorizontallyScrolling() {
3649 return mHorizontallyScrolling;
3650 }
3651
3652 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003653 * Makes the TextView at least this many lines tall.
3654 *
3655 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3656 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003657 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003658 * @see #getMinLines()
3659 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003660 * @attr ref android.R.styleable#TextView_minLines
3661 */
3662 @android.view.RemotableViewMethod
3663 public void setMinLines(int minlines) {
3664 mMinimum = minlines;
3665 mMinMode = LINES;
3666
3667 requestLayout();
3668 invalidate();
3669 }
3670
3671 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003672 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3673 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3674 *
3675 * @see #setMinLines(int)
3676 *
3677 * @attr ref android.R.styleable#TextView_minLines
3678 */
3679 public int getMinLines() {
3680 return mMinMode == LINES ? mMinimum : -1;
3681 }
3682
3683 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003684 * Makes the TextView at least this many pixels tall.
3685 *
3686 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003687 *
3688 * @attr ref android.R.styleable#TextView_minHeight
3689 */
3690 @android.view.RemotableViewMethod
3691 public void setMinHeight(int minHeight) {
3692 mMinimum = minHeight;
3693 mMinMode = PIXELS;
3694
3695 requestLayout();
3696 invalidate();
3697 }
3698
3699 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003700 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3701 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3702 *
3703 * @see #setMinHeight(int)
3704 *
3705 * @attr ref android.R.styleable#TextView_minHeight
3706 */
3707 public int getMinHeight() {
3708 return mMinMode == PIXELS ? mMinimum : -1;
3709 }
3710
3711 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003712 * Makes the TextView at most this many lines tall.
3713 *
3714 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003715 *
3716 * @attr ref android.R.styleable#TextView_maxLines
3717 */
3718 @android.view.RemotableViewMethod
3719 public void setMaxLines(int maxlines) {
3720 mMaximum = maxlines;
3721 mMaxMode = LINES;
3722
3723 requestLayout();
3724 invalidate();
3725 }
3726
3727 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003728 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3729 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3730 *
3731 * @see #setMaxLines(int)
3732 *
3733 * @attr ref android.R.styleable#TextView_maxLines
3734 */
3735 public int getMaxLines() {
3736 return mMaxMode == LINES ? mMaximum : -1;
3737 }
3738
3739 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003740 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3741 * {@link #setMaxLines(int)} method.
3742 *
3743 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003744 *
3745 * @attr ref android.R.styleable#TextView_maxHeight
3746 */
3747 @android.view.RemotableViewMethod
3748 public void setMaxHeight(int maxHeight) {
3749 mMaximum = maxHeight;
3750 mMaxMode = PIXELS;
3751
3752 requestLayout();
3753 invalidate();
3754 }
3755
3756 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003757 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3758 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3759 *
3760 * @see #setMaxHeight(int)
3761 *
3762 * @attr ref android.R.styleable#TextView_maxHeight
3763 */
3764 public int getMaxHeight() {
3765 return mMaxMode == PIXELS ? mMaximum : -1;
3766 }
3767
3768 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003769 * Makes the TextView exactly this many lines tall.
3770 *
3771 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3772 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003773 *
3774 * @attr ref android.R.styleable#TextView_lines
3775 */
3776 @android.view.RemotableViewMethod
3777 public void setLines(int lines) {
3778 mMaximum = mMinimum = lines;
3779 mMaxMode = mMinMode = LINES;
3780
3781 requestLayout();
3782 invalidate();
3783 }
3784
3785 /**
3786 * Makes the TextView exactly this many pixels tall.
3787 * You could do the same thing by specifying this number in the
3788 * LayoutParams.
3789 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003790 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3791 * height setting.
3792 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793 * @attr ref android.R.styleable#TextView_height
3794 */
3795 @android.view.RemotableViewMethod
3796 public void setHeight(int pixels) {
3797 mMaximum = mMinimum = pixels;
3798 mMaxMode = mMinMode = PIXELS;
3799
3800 requestLayout();
3801 invalidate();
3802 }
3803
3804 /**
3805 * Makes the TextView at least this many ems wide
3806 *
3807 * @attr ref android.R.styleable#TextView_minEms
3808 */
3809 @android.view.RemotableViewMethod
3810 public void setMinEms(int minems) {
3811 mMinWidth = minems;
3812 mMinWidthMode = EMS;
3813
3814 requestLayout();
3815 invalidate();
3816 }
3817
3818 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003819 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3820 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3821 *
3822 * @see #setMinEms(int)
3823 * @see #setEms(int)
3824 *
3825 * @attr ref android.R.styleable#TextView_minEms
3826 */
3827 public int getMinEms() {
3828 return mMinWidthMode == EMS ? mMinWidth : -1;
3829 }
3830
3831 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003832 * Makes the TextView at least this many pixels wide
3833 *
3834 * @attr ref android.R.styleable#TextView_minWidth
3835 */
3836 @android.view.RemotableViewMethod
3837 public void setMinWidth(int minpixels) {
3838 mMinWidth = minpixels;
3839 mMinWidthMode = PIXELS;
3840
3841 requestLayout();
3842 invalidate();
3843 }
3844
3845 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003846 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3847 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3848 *
3849 * @see #setMinWidth(int)
3850 * @see #setWidth(int)
3851 *
3852 * @attr ref android.R.styleable#TextView_minWidth
3853 */
3854 public int getMinWidth() {
3855 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3856 }
3857
3858 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003859 * Makes the TextView at most this many ems wide
3860 *
3861 * @attr ref android.R.styleable#TextView_maxEms
3862 */
3863 @android.view.RemotableViewMethod
3864 public void setMaxEms(int maxems) {
3865 mMaxWidth = maxems;
3866 mMaxWidthMode = EMS;
3867
3868 requestLayout();
3869 invalidate();
3870 }
3871
3872 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003873 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3874 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3875 *
3876 * @see #setMaxEms(int)
3877 * @see #setEms(int)
3878 *
3879 * @attr ref android.R.styleable#TextView_maxEms
3880 */
3881 public int getMaxEms() {
3882 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3883 }
3884
3885 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003886 * Makes the TextView at most this many pixels wide
3887 *
3888 * @attr ref android.R.styleable#TextView_maxWidth
3889 */
3890 @android.view.RemotableViewMethod
3891 public void setMaxWidth(int maxpixels) {
3892 mMaxWidth = maxpixels;
3893 mMaxWidthMode = PIXELS;
3894
3895 requestLayout();
3896 invalidate();
3897 }
3898
3899 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003900 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3901 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3902 *
3903 * @see #setMaxWidth(int)
3904 * @see #setWidth(int)
3905 *
3906 * @attr ref android.R.styleable#TextView_maxWidth
3907 */
3908 public int getMaxWidth() {
3909 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3910 }
3911
3912 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003913 * Makes the TextView exactly this many ems wide
3914 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003915 * @see #setMaxEms(int)
3916 * @see #setMinEms(int)
3917 * @see #getMinEms()
3918 * @see #getMaxEms()
3919 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003920 * @attr ref android.R.styleable#TextView_ems
3921 */
3922 @android.view.RemotableViewMethod
3923 public void setEms(int ems) {
3924 mMaxWidth = mMinWidth = ems;
3925 mMaxWidthMode = mMinWidthMode = EMS;
3926
3927 requestLayout();
3928 invalidate();
3929 }
3930
3931 /**
3932 * Makes the TextView exactly this many pixels wide.
3933 * You could do the same thing by specifying this number in the
3934 * LayoutParams.
3935 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003936 * @see #setMaxWidth(int)
3937 * @see #setMinWidth(int)
3938 * @see #getMinWidth()
3939 * @see #getMaxWidth()
3940 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003941 * @attr ref android.R.styleable#TextView_width
3942 */
3943 @android.view.RemotableViewMethod
3944 public void setWidth(int pixels) {
3945 mMaxWidth = mMinWidth = pixels;
3946 mMaxWidthMode = mMinWidthMode = PIXELS;
3947
3948 requestLayout();
3949 invalidate();
3950 }
3951
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003952 /**
3953 * Sets line spacing for this TextView. Each line will have its height
3954 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3955 *
3956 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3957 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3958 */
3959 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003960 if (mSpacingAdd != add || mSpacingMult != mult) {
3961 mSpacingAdd = add;
3962 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003963
Gilles Debunne22378292011-08-12 10:38:52 -07003964 if (mLayout != null) {
3965 nullLayouts();
3966 requestLayout();
3967 invalidate();
3968 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003969 }
3970 }
3971
3972 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003973 * Gets the line spacing multiplier
3974 *
3975 * @return the value by which each line's height is multiplied to get its actual height.
3976 *
3977 * @see #setLineSpacing(float, float)
3978 * @see #getLineSpacingExtra()
3979 *
3980 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3981 */
3982 public float getLineSpacingMultiplier() {
3983 return mSpacingMult;
3984 }
3985
3986 /**
3987 * Gets the line spacing extra space
3988 *
3989 * @return the extra space that is added to the height of each lines of this TextView.
3990 *
3991 * @see #setLineSpacing(float, float)
3992 * @see #getLineSpacingMultiplier()
3993 *
3994 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3995 */
3996 public float getLineSpacingExtra() {
3997 return mSpacingAdd;
3998 }
3999
4000 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004001 * Convenience method: Append the specified text to the TextView's
4002 * display buffer, upgrading it to BufferType.EDITABLE if it was
4003 * not already editable.
4004 */
4005 public final void append(CharSequence text) {
4006 append(text, 0, text.length());
4007 }
4008
4009 /**
4010 * Convenience method: Append the specified text slice to the TextView's
4011 * display buffer, upgrading it to BufferType.EDITABLE if it was
4012 * not already editable.
4013 */
4014 public void append(CharSequence text, int start, int end) {
4015 if (!(mText instanceof Editable)) {
4016 setText(mText, BufferType.EDITABLE);
4017 }
4018
4019 ((Editable) mText).append(text, start, end);
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004020
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004021 boolean hasClickableSpans = false;
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004022 if (mAutoLinkMask != 0) {
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004023 hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4024 } else if (mLinksClickable && text instanceof Spanned) {
4025 ClickableSpan[] clickableSpans =
4026 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4027 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4028 }
4029
4030 // Do not change the movement method for text that supports text selection as it
4031 // would prevent an arbitrary cursor displacement.
4032 if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
4033 setMovementMethod(LinkMovementMethod.getInstance());
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004034 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004035 }
4036
4037 private void updateTextColors() {
4038 boolean inval = false;
4039 int color = mTextColor.getColorForState(getDrawableState(), 0);
4040 if (color != mCurTextColor) {
4041 mCurTextColor = color;
4042 inval = true;
4043 }
4044 if (mLinkTextColor != null) {
4045 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4046 if (color != mTextPaint.linkColor) {
4047 mTextPaint.linkColor = color;
4048 inval = true;
4049 }
4050 }
4051 if (mHintTextColor != null) {
4052 color = mHintTextColor.getColorForState(getDrawableState(), 0);
Raph Levienc1bf2852014-11-12 12:41:24 -08004053 if (color != mCurHintTextColor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004054 mCurHintTextColor = color;
Raph Levienc1bf2852014-11-12 12:41:24 -08004055 if (mText.length() == 0) {
4056 inval = true;
4057 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004058 }
4059 }
4060 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07004061 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07004062 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004063 invalidate();
4064 }
4065 }
4066
4067 @Override
4068 protected void drawableStateChanged() {
4069 super.drawableStateChanged();
Alan Viverettead0020f2015-09-04 10:10:42 -04004070
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004071 if (mTextColor != null && mTextColor.isStateful()
4072 || (mHintTextColor != null && mHintTextColor.isStateful())
4073 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4074 updateTextColors();
4075 }
4076
Alan Viveretteb97d6982015-01-07 16:16:20 -08004077 if (mDrawables != null) {
4078 final int[] state = getDrawableState();
4079 for (Drawable dr : mDrawables.mShowing) {
Alan Viverettead0020f2015-09-04 10:10:42 -04004080 if (dr != null && dr.isStateful() && dr.setState(state)) {
4081 invalidateDrawable(dr);
Alan Viveretteb97d6982015-01-07 16:16:20 -08004082 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004083 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004084 }
4085 }
4086
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004087 @Override
Alan Viverette8de14942014-06-18 18:05:15 -07004088 public void drawableHotspotChanged(float x, float y) {
4089 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004090
Alan Viveretteb97d6982015-01-07 16:16:20 -08004091 if (mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08004092 for (Drawable dr : mDrawables.mShowing) {
Alan Viverettead0020f2015-09-04 10:10:42 -04004093 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08004094 dr.setHotspot(x, y);
4095 }
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004096 }
4097 }
4098 }
4099
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004100 @Override
4101 public Parcelable onSaveInstanceState() {
4102 Parcelable superState = super.onSaveInstanceState();
4103
4104 // Save state if we are forced to
Siyamed Sinir02771192016-02-05 16:08:59 -08004105 final boolean freezesText = getFreezesText();
4106 boolean hasSelection = false;
4107 int start = -1;
4108 int end = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004109
4110 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004111 start = getSelectionStart();
4112 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004113 if (start >= 0 || end >= 0) {
4114 // Or save state if there is a selection
Siyamed Sinir02771192016-02-05 16:08:59 -08004115 hasSelection = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004116 }
4117 }
4118
Siyamed Sinir02771192016-02-05 16:08:59 -08004119 if (freezesText || hasSelection) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004120 SavedState ss = new SavedState(superState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004121
Siyamed Sinir02771192016-02-05 16:08:59 -08004122 if (freezesText) {
4123 if (mText instanceof Spanned) {
4124 final Spannable sp = new SpannableStringBuilder(mText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004125
Siyamed Sinir02771192016-02-05 16:08:59 -08004126 if (mEditor != null) {
4127 removeMisspelledSpans(sp);
4128 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4129 }
4130
4131 ss.text = sp;
4132 } else {
4133 ss.text = mText.toString();
Gilles Debunne60e21862012-01-30 15:04:14 -08004134 }
Siyamed Sinir02771192016-02-05 16:08:59 -08004135 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07004136
Siyamed Sinir02771192016-02-05 16:08:59 -08004137 if (hasSelection) {
4138 // XXX Should also save the current scroll position!
4139 ss.selStart = start;
4140 ss.selEnd = end;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004141 }
4142
4143 if (isFocused() && start >= 0 && end >= 0) {
4144 ss.frozenWithFocus = true;
4145 }
4146
Gilles Debunne60e21862012-01-30 15:04:14 -08004147 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08004148
James Cookf59152c2015-02-26 18:03:58 -08004149 if (mEditor != null) {
4150 ss.editorState = mEditor.saveInstanceState();
4151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004152 return ss;
4153 }
4154
4155 return superState;
4156 }
4157
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07004158 void removeMisspelledSpans(Spannable spannable) {
4159 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4160 SuggestionSpan.class);
4161 for (int i = 0; i < suggestionSpans.length; i++) {
4162 int flags = suggestionSpans[i].getFlags();
4163 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4164 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4165 spannable.removeSpan(suggestionSpans[i]);
4166 }
4167 }
4168 }
4169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004170 @Override
4171 public void onRestoreInstanceState(Parcelable state) {
4172 if (!(state instanceof SavedState)) {
4173 super.onRestoreInstanceState(state);
4174 return;
4175 }
4176
4177 SavedState ss = (SavedState)state;
4178 super.onRestoreInstanceState(ss.getSuperState());
4179
4180 // XXX restore buffer type too, as well as lots of other stuff
4181 if (ss.text != null) {
4182 setText(ss.text);
4183 }
4184
4185 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4186 if (mText instanceof Spannable) {
4187 int len = mText.length();
4188
4189 if (ss.selStart > len || ss.selEnd > len) {
4190 String restored = "";
4191
4192 if (ss.text != null) {
4193 restored = "(restored) ";
4194 }
4195
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004196 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004197 "/" + ss.selEnd + " out of range for " + restored +
4198 "text " + mText);
4199 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08004200 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004201
4202 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004203 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004204 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004205 }
4206 }
4207 }
4208 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08004209
4210 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07004211 final CharSequence error = ss.error;
4212 // Display the error later, after the first layout pass
4213 post(new Runnable() {
4214 public void run() {
Alexander Toresson545a8bb2013-10-14 14:59:31 +02004215 if (mEditor == null || !mEditor.mErrorWasChanged) {
4216 setError(error);
4217 }
Romain Guy9bc9fa12009-07-21 16:57:29 -07004218 }
4219 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08004220 }
James Cookf59152c2015-02-26 18:03:58 -08004221
4222 if (ss.editorState != null) {
4223 createEditorIfNeeded();
4224 mEditor.restoreInstanceState(ss.editorState);
4225 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004226 }
4227
4228 /**
4229 * Control whether this text view saves its entire text contents when
4230 * freezing to an icicle, in addition to dynamic state such as cursor
4231 * position. By default this is false, not saving the text. Set to true
4232 * if the text in the text view is not being saved somewhere else in
4233 * persistent storage (such as in a content provider) so that if the
Siyamed Sinir02771192016-02-05 16:08:59 -08004234 * view is later thawed the user will not lose their data. For
4235 * {@link android.widget.EditText} it is always enabled, regardless of
4236 * the value of the attribute.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004237 *
4238 * @param freezesText Controls whether a frozen icicle should include the
4239 * entire text data: true to include it, false to not.
4240 *
4241 * @attr ref android.R.styleable#TextView_freezesText
4242 */
4243 @android.view.RemotableViewMethod
4244 public void setFreezesText(boolean freezesText) {
4245 mFreezesText = freezesText;
4246 }
4247
4248 /**
4249 * Return whether this text view is including its entire text contents
Siyamed Sinir02771192016-02-05 16:08:59 -08004250 * in frozen icicles. For {@link android.widget.EditText} it always returns true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004251 *
4252 * @return Returns true if text is included, false if it isn't.
4253 *
4254 * @see #setFreezesText
4255 */
4256 public boolean getFreezesText() {
4257 return mFreezesText;
4258 }
4259
4260 ///////////////////////////////////////////////////////////////////////////
4261
4262 /**
4263 * Sets the Factory used to create new Editables.
4264 */
4265 public final void setEditableFactory(Editable.Factory factory) {
4266 mEditableFactory = factory;
4267 setText(mText);
4268 }
4269
4270 /**
4271 * Sets the Factory used to create new Spannables.
4272 */
4273 public final void setSpannableFactory(Spannable.Factory factory) {
4274 mSpannableFactory = factory;
4275 setText(mText);
4276 }
4277
4278 /**
4279 * Sets the string value of the TextView. TextView <em>does not</em> accept
4280 * HTML-like formatting, which you can do with text strings in XML resource files.
4281 * To style your strings, attach android.text.style.* objects to a
4282 * {@link android.text.SpannableString SpannableString}, or see the
4283 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07004284 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004285 * formatted text in the XML resource file.
4286 *
4287 * @attr ref android.R.styleable#TextView_text
4288 */
4289 @android.view.RemotableViewMethod
4290 public final void setText(CharSequence text) {
4291 setText(text, mBufferType);
4292 }
4293
4294 /**
4295 * Like {@link #setText(CharSequence)},
4296 * except that the cursor position (if any) is retained in the new text.
4297 *
4298 * @param text The new text to place in the text view.
4299 *
4300 * @see #setText(CharSequence)
4301 */
4302 @android.view.RemotableViewMethod
4303 public final void setTextKeepState(CharSequence text) {
4304 setTextKeepState(text, mBufferType);
4305 }
4306
4307 /**
4308 * Sets the text that this TextView is to display (see
4309 * {@link #setText(CharSequence)}) and also sets whether it is stored
4310 * in a styleable/spannable buffer and whether it is editable.
4311 *
4312 * @attr ref android.R.styleable#TextView_text
4313 * @attr ref android.R.styleable#TextView_bufferType
4314 */
4315 public void setText(CharSequence text, BufferType type) {
4316 setText(text, type, true, 0);
4317
4318 if (mCharWrapper != null) {
4319 mCharWrapper.mChars = null;
4320 }
4321 }
4322
4323 private void setText(CharSequence text, BufferType type,
4324 boolean notifyBefore, int oldlen) {
4325 if (text == null) {
4326 text = "";
4327 }
4328
Luca Zanoline0760452011-09-08 12:03:37 +01004329 // If suggestions are not enabled, remove the suggestion spans from the text
4330 if (!isSuggestionsEnabled()) {
4331 text = removeSuggestionSpans(text);
4332 }
4333
Romain Guy939151f2009-04-08 14:22:40 -07004334 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004336 if (text instanceof Spanned &&
4337 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07004338 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4339 setHorizontalFadingEdgeEnabled(true);
4340 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4341 } else {
4342 setHorizontalFadingEdgeEnabled(false);
4343 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4344 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004345 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4346 }
4347
4348 int n = mFilters.length;
4349 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08004350 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004351 if (out != null) {
4352 text = out;
4353 }
4354 }
4355
4356 if (notifyBefore) {
4357 if (mText != null) {
4358 oldlen = mText.length();
4359 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4360 } else {
4361 sendBeforeTextChanged("", 0, 0, text.length());
4362 }
4363 }
4364
4365 boolean needEditableForNotification = false;
4366
4367 if (mListeners != null && mListeners.size() != 0) {
4368 needEditableForNotification = true;
4369 }
4370
Gilles Debunne2d373a12012-04-20 15:32:19 -07004371 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4372 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004373 createEditorIfNeeded();
James Cook48e0fac2015-02-25 15:44:51 -08004374 mEditor.forgetUndoRedo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004375 Editable t = mEditableFactory.newEditable(text);
4376 text = t;
4377 setFilters(t, mFilters);
4378 InputMethodManager imm = InputMethodManager.peekInstance();
4379 if (imm != null) imm.restartInput(this);
4380 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4381 text = mSpannableFactory.newSpannable(text);
4382 } else if (!(text instanceof CharWrapper)) {
4383 text = TextUtils.stringOrSpannedString(text);
4384 }
4385
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004386 boolean hasClickableSpans = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004387 if (mAutoLinkMask != 0) {
4388 Spannable s2;
4389
4390 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4391 s2 = (Spannable) text;
4392 } else {
4393 s2 = mSpannableFactory.newSpannable(text);
4394 }
4395
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004396 hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
4397 if (hasClickableSpans) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004398 text = s2;
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004399 }
4400 } else if (mLinksClickable && text instanceof Spanned) {
4401 ClickableSpan[] clickableSpans =
4402 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4403 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4404 if (hasClickableSpans && !(text instanceof Spannable)) {
4405 text = mSpannableFactory.newSpannable(text);
4406 }
4407 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004408
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004409 if (hasClickableSpans) {
4410 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4411 /*
4412 * We must go ahead and set the text before changing the
4413 * movement method, because setMovementMethod() may call
4414 * setText() again to try to upgrade the buffer type.
4415 */
4416 mText = text;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004417
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004418 // Do not change the movement method for text that supports text selection as it
4419 // would prevent an arbitrary cursor displacement.
4420 if (mLinksClickable && !textCanBeSelected()) {
4421 setMovementMethod(LinkMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004422 }
4423 }
4424
4425 mBufferType = type;
4426 mText = text;
4427
Adam Powell7f8f79a2011-07-07 18:35:54 -07004428 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004429 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07004430 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004431 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07004432 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004433
4434 final int textLength = text.length();
4435
Adam Powell7f8f79a2011-07-07 18:35:54 -07004436 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004437 Spannable sp = (Spannable) text;
4438
Gilles Debunnec62589c2012-04-12 14:50:23 -07004439 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004440 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4441 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07004442 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004443 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07004444 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004445
Gilles Debunnec62589c2012-04-12 14:50:23 -07004446 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004447
4448 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08004449 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004450
Gilles Debunnec62589c2012-04-12 14:50:23 -07004451 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004452
4453 if (mTransformation != null) {
4454 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004455 }
4456
4457 if (mMovement != null) {
4458 mMovement.initialize(this, (Spannable) text);
4459
4460 /*
4461 * Initializing the movement method will have set the
4462 * selection, so reset mSelectionMoved to keep that from
4463 * interfering with the normal on-focus selection-setting.
4464 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07004465 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004466 }
4467 }
4468
4469 if (mLayout != null) {
4470 checkForRelayout();
4471 }
4472
4473 sendOnTextChanged(text, 0, oldlen, textLength);
4474 onTextChanged(text, 0, oldlen, textLength);
4475
Alan Viverette77e9a282013-09-12 17:16:09 -07004476 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4477
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004478 if (needEditableForNotification) {
4479 sendAfterTextChanged((Editable) text);
4480 }
Gilles Debunne05336272010-07-09 20:13:45 -07004481
Gilles Debunnebaaace52010-10-01 15:47:13 -07004482 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07004483 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004484 }
4485
4486 /**
4487 * Sets the TextView to display the specified slice of the specified
4488 * char array. You must promise that you will not change the contents
4489 * of the array except for right before another call to setText(),
4490 * since the TextView has no way to know that the text
4491 * has changed and that it needs to invalidate and re-layout.
4492 */
4493 public final void setText(char[] text, int start, int len) {
4494 int oldlen = 0;
4495
4496 if (start < 0 || len < 0 || start + len > text.length) {
4497 throw new IndexOutOfBoundsException(start + ", " + len);
4498 }
4499
4500 /*
4501 * We must do the before-notification here ourselves because if
4502 * the old text is a CharWrapper we destroy it before calling
4503 * into the normal path.
4504 */
4505 if (mText != null) {
4506 oldlen = mText.length();
4507 sendBeforeTextChanged(mText, 0, oldlen, len);
4508 } else {
4509 sendBeforeTextChanged("", 0, 0, len);
4510 }
4511
4512 if (mCharWrapper == null) {
4513 mCharWrapper = new CharWrapper(text, start, len);
4514 } else {
4515 mCharWrapper.set(text, start, len);
4516 }
4517
4518 setText(mCharWrapper, mBufferType, false, oldlen);
4519 }
4520
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004521 /**
4522 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4523 * except that the cursor position (if any) is retained in the new text.
4524 *
4525 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4526 */
4527 public final void setTextKeepState(CharSequence text, BufferType type) {
4528 int start = getSelectionStart();
4529 int end = getSelectionEnd();
4530 int len = text.length();
4531
4532 setText(text, type);
4533
4534 if (start >= 0 || end >= 0) {
4535 if (mText instanceof Spannable) {
4536 Selection.setSelection((Spannable) mText,
4537 Math.max(0, Math.min(start, len)),
4538 Math.max(0, Math.min(end, len)));
4539 }
4540 }
4541 }
4542
4543 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004544 public final void setText(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004545 setText(getContext().getResources().getText(resid));
4546 }
4547
Tor Norbye7b9c9122013-05-30 16:48:33 -07004548 public final void setText(@StringRes int resid, BufferType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004549 setText(getContext().getResources().getText(resid), type);
4550 }
4551
4552 /**
4553 * Sets the text to be displayed when the text of the TextView is empty.
4554 * Null means to use the normal empty text. The hint does not currently
4555 * participate in determining the size of the view.
4556 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004557 * @attr ref android.R.styleable#TextView_hint
4558 */
4559 @android.view.RemotableViewMethod
4560 public final void setHint(CharSequence hint) {
4561 mHint = TextUtils.stringOrSpannedString(hint);
4562
4563 if (mLayout != null) {
4564 checkForRelayout();
4565 }
4566
Romain Guy4dc4f732009-06-19 15:16:40 -07004567 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004568 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07004569 }
Gilles Debunne626c3162012-02-14 15:46:41 -08004570
Gilles Debunne33b7de852012-03-12 11:57:48 -07004571 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08004572 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004573 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08004574 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004575 }
4576
4577 /**
4578 * Sets the text to be displayed when the text of the TextView is empty,
4579 * from a resource.
4580 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004581 * @attr ref android.R.styleable#TextView_hint
4582 */
4583 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004584 public final void setHint(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004585 setHint(getContext().getResources().getText(resid));
4586 }
4587
4588 /**
4589 * Returns the hint that is displayed when the text of the TextView
4590 * is empty.
4591 *
4592 * @attr ref android.R.styleable#TextView_hint
4593 */
4594 @ViewDebug.CapturedViewProperty
4595 public CharSequence getHint() {
4596 return mHint;
4597 }
4598
Gilles Debunned88876a2012-03-16 17:34:04 -07004599 boolean isSingleLine() {
4600 return mSingleLine;
4601 }
4602
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004603 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004604 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4605 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4606 }
4607
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004608 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07004609 * Removes the suggestion spans.
4610 */
4611 CharSequence removeSuggestionSpans(CharSequence text) {
4612 if (text instanceof Spanned) {
4613 Spannable spannable;
4614 if (text instanceof Spannable) {
4615 spannable = (Spannable) text;
4616 } else {
4617 spannable = new SpannableString(text);
4618 text = spannable;
4619 }
4620
4621 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4622 for (int i = 0; i < spans.length; i++) {
4623 spannable.removeSpan(spans[i]);
4624 }
4625 }
4626 return text;
4627 }
4628
4629 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004630 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4631 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4632 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4633 * then a soft keyboard will not be displayed for this text view.
4634 *
4635 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4636 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4637 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004638 *
4639 * @see #getInputType()
4640 * @see #setRawInputType(int)
4641 * @see android.text.InputType
4642 * @attr ref android.R.styleable#TextView_inputType
4643 */
4644 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004645 final boolean wasPassword = isPasswordInputType(getInputType());
4646 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004647 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004648 final boolean isPassword = isPasswordInputType(type);
4649 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004650 boolean forceUpdate = false;
4651 if (isPassword) {
4652 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07004653 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004654 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07004655 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4656 forceUpdate = true;
4657 }
Raph Leviend570e892012-05-09 11:45:34 -07004658 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004659 } else if (wasPassword || wasVisiblePassword) {
4660 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004661 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004662 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4663 forceUpdate = true;
4664 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004665 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004666
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004667 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004668
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004669 // We need to update the single line mode if it has changed or we
4670 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004671 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004672 // Change single line mode, but only change the transformation if
4673 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004674 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004675 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004676
Luca Zanoline0760452011-09-08 12:03:37 +01004677 if (!isSuggestionsEnabled()) {
4678 mText = removeSuggestionSpans(mText);
4679 }
4680
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004681 InputMethodManager imm = InputMethodManager.peekInstance();
4682 if (imm != null) imm.restartInput(this);
4683 }
4684
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004685 /**
4686 * It would be better to rely on the input type for everything. A password inputType should have
4687 * a password transformation. We should hence use isPasswordInputType instead of this method.
4688 *
4689 * We should:
4690 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4691 * would install the correct transformation).
4692 * - Refuse the installation of a non-password transformation in setTransformation if the input
4693 * type is password.
4694 *
4695 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4696 * is useful since it matches what the user can see (obfuscated text or not).
4697 *
4698 * @return true if the current transformation method is of the password type.
4699 */
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01004700 boolean hasPasswordTransformationMethod() {
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004701 return mTransformation instanceof PasswordTransformationMethod;
4702 }
4703
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004704 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004705 final int variation =
4706 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004707 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004708 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4709 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004710 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4711 || variation
4712 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004713 }
4714
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004715 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004716 final int variation =
4717 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004718 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004719 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004720 }
4721
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004722 /**
4723 * Directly change the content type integer of the text view, without
4724 * modifying any other state.
4725 * @see #setInputType(int)
4726 * @see android.text.InputType
4727 * @attr ref android.R.styleable#TextView_inputType
4728 */
4729 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004730 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004731 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004732 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004733 }
4734
4735 private void setInputType(int type, boolean direct) {
4736 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4737 KeyListener input;
4738 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004739 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004740 TextKeyListener.Capitalize cap;
4741 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4742 cap = TextKeyListener.Capitalize.CHARACTERS;
4743 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4744 cap = TextKeyListener.Capitalize.WORDS;
4745 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4746 cap = TextKeyListener.Capitalize.SENTENCES;
4747 } else {
4748 cap = TextKeyListener.Capitalize.NONE;
4749 }
4750 input = TextKeyListener.getInstance(autotext, cap);
4751 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4752 input = DigitsKeyListener.getInstance(
4753 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4754 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4755 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4756 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4757 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4758 input = DateKeyListener.getInstance();
4759 break;
4760 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4761 input = TimeKeyListener.getInstance();
4762 break;
4763 default:
4764 input = DateTimeKeyListener.getInstance();
4765 break;
4766 }
4767 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4768 input = DialerKeyListener.getInstance();
4769 } else {
4770 input = TextKeyListener.getInstance();
4771 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004772 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004773 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004774 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004775 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004776 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004777 setKeyListenerOnly(input);
4778 }
4779 }
4780
4781 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004782 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004783 *
4784 * @see #setInputType(int)
4785 * @see android.text.InputType
4786 */
4787 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004788 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004789 }
4790
4791 /**
4792 * Change the editor type integer associated with the text view, which
4793 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4794 * has focus.
4795 * @see #getImeOptions
4796 * @see android.view.inputmethod.EditorInfo
4797 * @attr ref android.R.styleable#TextView_imeOptions
4798 */
4799 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004800 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004801 mEditor.createInputContentTypeIfNeeded();
4802 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004803 }
4804
4805 /**
4806 * Get the type of the IME editor.
4807 *
4808 * @see #setImeOptions(int)
4809 * @see android.view.inputmethod.EditorInfo
4810 */
4811 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004812 return mEditor != null && mEditor.mInputContentType != null
4813 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004814 }
4815
4816 /**
4817 * Change the custom IME action associated with the text view, which
4818 * will be reported to an IME with {@link EditorInfo#actionLabel}
4819 * and {@link EditorInfo#actionId} when it has focus.
4820 * @see #getImeActionLabel
4821 * @see #getImeActionId
4822 * @see android.view.inputmethod.EditorInfo
4823 * @attr ref android.R.styleable#TextView_imeActionLabel
4824 * @attr ref android.R.styleable#TextView_imeActionId
4825 */
4826 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004827 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004828 mEditor.createInputContentTypeIfNeeded();
4829 mEditor.mInputContentType.imeActionLabel = label;
4830 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004831 }
4832
4833 /**
4834 * Get the IME action label previous set with {@link #setImeActionLabel}.
4835 *
4836 * @see #setImeActionLabel
4837 * @see android.view.inputmethod.EditorInfo
4838 */
4839 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004840 return mEditor != null && mEditor.mInputContentType != null
4841 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004842 }
4843
4844 /**
4845 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4846 *
4847 * @see #setImeActionLabel
4848 * @see android.view.inputmethod.EditorInfo
4849 */
4850 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004851 return mEditor != null && mEditor.mInputContentType != null
4852 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004853 }
4854
4855 /**
4856 * Set a special listener to be called when an action is performed
4857 * on the text view. This will be called when the enter key is pressed,
4858 * or when an action supplied to the IME is selected by the user. Setting
4859 * this means that the normal hard key event will not insert a newline
4860 * into the text view, even if it is multi-line; holding down the ALT
4861 * modifier will, however, allow the user to insert a newline character.
4862 */
4863 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004864 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004865 mEditor.createInputContentTypeIfNeeded();
4866 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004867 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004868
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004869 /**
4870 * Called when an attached input method calls
4871 * {@link InputConnection#performEditorAction(int)
4872 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004873 * for this text view. The default implementation will call your action
4874 * listener supplied to {@link #setOnEditorActionListener}, or perform
4875 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004876 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4877 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004878 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004879 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004880 * <p>For backwards compatibility, if no IME options have been set and the
4881 * text view would not normally advance focus on enter, then
4882 * the NEXT and DONE actions received here will be turned into an enter
4883 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004884 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004885 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004886 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004887 * @see #setOnEditorActionListener
4888 */
4889 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004890 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004891 if (ict != null) {
4892 if (ict.onEditorActionListener != null) {
4893 if (ict.onEditorActionListener.onEditorAction(this,
4894 actionCode, null)) {
4895 return;
4896 }
4897 }
Gilles Debunne64794482011-11-30 15:45:28 -08004898
The Android Open Source Project4df24232009-03-05 14:34:35 -08004899 // This is the handling for some default action.
4900 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004901 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004902 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004903 // app may be expecting.
4904 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004905 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004906 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004907 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004908 throw new IllegalStateException("focus search returned a view " +
4909 "that wasn't able to take focus!");
4910 }
4911 }
4912 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004913
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004914 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004915 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004916 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004917 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004918 throw new IllegalStateException("focus search returned a view " +
4919 "that wasn't able to take focus!");
4920 }
4921 }
4922 return;
4923
The Android Open Source Project4df24232009-03-05 14:34:35 -08004924 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4925 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004926 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004927 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004928 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004929 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004930 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004931 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004932
Jeff Browna175a5b2012-02-15 19:18:31 -08004933 ViewRootImpl viewRootImpl = getViewRootImpl();
4934 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004935 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004936 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004937 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004938 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4939 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004940 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004941 | KeyEvent.FLAG_EDITOR_ACTION));
4942 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004943 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004944 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4945 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004946 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004947 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004948 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004949 }
Gilles Debunne64794482011-11-30 15:45:28 -08004950
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004951 /**
4952 * Set the private content type of the text, which is the
4953 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4954 * field that will be filled in when creating an input connection.
4955 *
4956 * @see #getPrivateImeOptions()
4957 * @see EditorInfo#privateImeOptions
4958 * @attr ref android.R.styleable#TextView_privateImeOptions
4959 */
4960 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004961 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004962 mEditor.createInputContentTypeIfNeeded();
4963 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004964 }
4965
4966 /**
4967 * Get the private type of the content.
4968 *
4969 * @see #setPrivateImeOptions(String)
4970 * @see EditorInfo#privateImeOptions
4971 */
4972 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004973 return mEditor != null && mEditor.mInputContentType != null
4974 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004975 }
4976
4977 /**
4978 * Set the extra input data of the text, which is the
4979 * {@link EditorInfo#extras TextBoxAttribute.extras}
4980 * Bundle that will be filled in when creating an input connection. The
4981 * given integer is the resource ID of an XML resource holding an
4982 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4983 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004984 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004985 * @see EditorInfo#extras
4986 * @attr ref android.R.styleable#TextView_editorExtras
4987 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07004988 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004989 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004990 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004991 mEditor.createInputContentTypeIfNeeded();
4992 mEditor.mInputContentType.extras = new Bundle();
4993 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004994 }
4995
4996 /**
4997 * Retrieve the input extras currently associated with the text view, which
4998 * can be viewed as well as modified.
4999 *
5000 * @param create If true, the extras will be created if they don't already
5001 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07005002 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005003 * @see EditorInfo#extras
5004 * @attr ref android.R.styleable#TextView_editorExtras
5005 */
5006 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005007 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07005008 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005009 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005010 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005011 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005012 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005013 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005014 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005015 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005016 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005017 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005018 }
5019
5020 /**
Yohei Yukawad469f212016-01-21 12:38:09 -08005021 * Change "hint" locales associated with the text view, which will be reported to an IME with
5022 * {@link EditorInfo#hintLocales} when it has focus.
5023 *
5024 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5025 * call {@link InputMethodManager#restartInput(View)}.</p>
5026 * @param hintLocales List of the languages that the user is supposed to switch to no matter
5027 * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5028 * @see #getImeHIntLocales()
5029 * @see android.view.inputmethod.EditorInfo#hintLocales
5030 */
5031 public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5032 createEditorIfNeeded();
5033 mEditor.createInputContentTypeIfNeeded();
5034 mEditor.mInputContentType.imeHintLocales = hintLocales;
5035 }
5036
5037 /**
5038 * @return The current languages list "hint". {@code null} when no "hint" is available.
5039 * @see #setImeHintLocales(LocaleList)
5040 * @see android.view.inputmethod.EditorInfo#hintLocales
5041 */
5042 @Nullable
5043 public LocaleList getImeHintLocales() {
5044 if (mEditor == null) { return null; }
5045 if (mEditor.mInputContentType == null) { return null; }
5046 return mEditor.mInputContentType.imeHintLocales;
5047 }
5048
5049 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005050 * Returns the error message that was set to be displayed with
5051 * {@link #setError}, or <code>null</code> if no error was set
5052 * or if it the error was cleared by the widget after user input.
5053 */
5054 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005055 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005056 }
5057
5058 /**
5059 * Sets the right-hand compound drawable of the TextView to the "error"
5060 * icon and sets an error message that will be displayed in a popup when
5061 * the TextView has focus. The icon and error message will be reset to
5062 * null when any key events cause changes to the TextView's text. If the
5063 * <code>error</code> is <code>null</code>, the error message and icon
5064 * will be cleared.
5065 */
5066 @android.view.RemotableViewMethod
5067 public void setError(CharSequence error) {
5068 if (error == null) {
5069 setError(null, null);
5070 } else {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08005071 Drawable dr = getContext().getDrawable(
5072 com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005073
5074 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5075 setError(error, dr);
5076 }
5077 }
5078
5079 /**
5080 * Sets the right-hand compound drawable of the TextView to the specified
5081 * icon and sets an error message that will be displayed in a popup when
5082 * the TextView has focus. The icon and error message will be reset to
5083 * null when any key events cause changes to the TextView's text. The
5084 * drawable must already have had {@link Drawable#setBounds} set on it.
5085 * If the <code>error</code> is <code>null</code>, the error message will
5086 * be cleared (and you should provide a <code>null</code> icon as well).
5087 */
5088 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005089 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005090 mEditor.setError(error, icon);
Alan Viverette77e9a282013-09-12 17:16:09 -07005091 notifyViewAccessibilityStateChangedIfNeeded(
5092 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005093 }
5094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005095 @Override
5096 protected boolean setFrame(int l, int t, int r, int b) {
5097 boolean result = super.setFrame(l, t, r, b);
5098
Gilles Debunne2d373a12012-04-20 15:32:19 -07005099 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005100
Romain Guy986003d2009-03-25 17:42:35 -07005101 restartMarqueeIfNeeded();
5102
5103 return result;
5104 }
5105
5106 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005107 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5108 mRestartMarquee = false;
5109 startMarquee();
5110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005111 }
5112
5113 /**
5114 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08005115 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005116 *
5117 * @attr ref android.R.styleable#TextView_maxLength
5118 */
5119 public void setFilters(InputFilter[] filters) {
5120 if (filters == null) {
5121 throw new IllegalArgumentException();
5122 }
5123
5124 mFilters = filters;
5125
5126 if (mText instanceof Editable) {
5127 setFilters((Editable) mText, filters);
5128 }
5129 }
5130
5131 /**
5132 * Sets the list of input filters on the specified Editable,
5133 * and includes mInput in the list if it is an InputFilter.
5134 */
5135 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005136 if (mEditor != null) {
5137 final boolean undoFilter = mEditor.mUndoInputFilter != null;
5138 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5139 int num = 0;
5140 if (undoFilter) num++;
5141 if (keyFilter) num++;
5142 if (num > 0) {
5143 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005144
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005145 System.arraycopy(filters, 0, nf, 0, filters.length);
5146 num = 0;
5147 if (undoFilter) {
5148 nf[filters.length] = mEditor.mUndoInputFilter;
5149 num++;
5150 }
5151 if (keyFilter) {
5152 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005154
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005155 e.setFilters(nf);
5156 return;
5157 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005158 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005159 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005160 }
5161
5162 /**
5163 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07005164 *
5165 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005166 */
5167 public InputFilter[] getFilters() {
5168 return mFilters;
5169 }
5170
5171 /////////////////////////////////////////////////////////////////////////
5172
Philip Milne7b757812012-09-19 18:13:44 -07005173 private int getBoxHeight(Layout l) {
5174 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5175 int padding = (l == mHintLayout) ?
5176 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5177 getExtendedPaddingTop() + getExtendedPaddingBottom();
5178 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5179 }
5180
Gilles Debunned88876a2012-03-16 17:34:04 -07005181 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005182 int voffset = 0;
5183 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5184
5185 Layout l = mLayout;
5186 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5187 l = mHintLayout;
5188 }
5189
5190 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07005191 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005192 int textht = l.getHeight();
5193
5194 if (textht < boxht) {
5195 if (gravity == Gravity.BOTTOM)
5196 voffset = boxht - textht;
5197 else // (gravity == Gravity.CENTER_VERTICAL)
5198 voffset = (boxht - textht) >> 1;
5199 }
5200 }
5201 return voffset;
5202 }
5203
5204 private int getBottomVerticalOffset(boolean forceNormal) {
5205 int voffset = 0;
5206 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5207
5208 Layout l = mLayout;
5209 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5210 l = mHintLayout;
5211 }
5212
5213 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07005214 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005215 int textht = l.getHeight();
5216
5217 if (textht < boxht) {
5218 if (gravity == Gravity.TOP)
5219 voffset = boxht - textht;
5220 else // (gravity == Gravity.CENTER_VERTICAL)
5221 voffset = (boxht - textht) >> 1;
5222 }
5223 }
5224 return voffset;
5225 }
5226
Gilles Debunned88876a2012-03-16 17:34:04 -07005227 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08005228 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005229 invalidateCursor();
5230 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005231 final int horizontalPadding = getCompoundPaddingLeft();
5232 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005233
Gilles Debunne2d373a12012-04-20 15:32:19 -07005234 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005235 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005236 /*
5237 * The reason for this concern about the thickness of the
5238 * cursor and doing the floor/ceil on the coordinates is that
5239 * some EditTexts (notably textfields in the Browser) have
5240 * anti-aliased text where not all the characters are
5241 * necessarily at integer-multiple locations. This should
5242 * make sure the entire cursor gets invalidated instead of
5243 * sometimes missing half a pixel.
5244 */
Neil Fuller33253a42014-10-01 11:55:10 +01005245 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005246 if (thick < 1.0f) {
5247 thick = 1.0f;
5248 }
5249
5250 thick /= 2.0f;
5251
Gilles Debunne83051b82012-02-24 20:01:13 -08005252 // mHighlightPath is guaranteed to be non null at that point.
5253 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005254
Neil Fuller33253a42014-10-01 11:55:10 +01005255 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5256 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5257 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5258 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005259 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005260 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005261 for (int i = 0; i < mEditor.mCursorCount; i++) {
5262 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005263 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5264 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5265 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005266 }
5267 }
5268 }
5269
Gilles Debunned88876a2012-03-16 17:34:04 -07005270 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07005271 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005272
5273 invalidateCursor(where, where, where);
5274 }
5275
5276 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005277 if (a >= 0 || b >= 0 || c >= 0) {
5278 int start = Math.min(Math.min(a, b), c);
5279 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08005280 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005281 }
5282 }
5283
5284 /**
5285 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08005286 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08005287 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005288 if (mLayout == null) {
5289 invalidate();
5290 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005291 int lineStart = mLayout.getLineForOffset(start);
5292 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005293
5294 // This is ridiculous, but the descent from the line above
5295 // can hang down into the line we really want to redraw,
5296 // so we have to invalidate part of the line above to make
5297 // sure everything that needs to be redrawn really is.
5298 // (But not the whole line above, because that would cause
5299 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08005300 if (lineStart > 0) {
5301 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005302 }
5303
Gilles Debunne8615ac92011-11-29 15:25:03 -08005304 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005305
Gilles Debunne8615ac92011-11-29 15:25:03 -08005306 if (start == end)
5307 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005308 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08005309 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005310
Gilles Debunne8615ac92011-11-29 15:25:03 -08005311 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005312
Gilles Debunne83051b82012-02-24 20:01:13 -08005313 // mEditor can be null in case selection is set programmatically.
5314 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005315 for (int i = 0; i < mEditor.mCursorCount; i++) {
5316 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08005317 top = Math.min(top, bounds.top);
5318 bottom = Math.max(bottom, bounds.bottom);
5319 }
5320 }
5321
Gilles Debunne8615ac92011-11-29 15:25:03 -08005322 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005323 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005324
5325 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08005326 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005327 left = (int) mLayout.getPrimaryHorizontal(start);
5328 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5329 left += compoundPaddingLeft;
5330 right += compoundPaddingLeft;
5331 } else {
5332 // Rectangle bounding box when the region spans several lines
5333 left = compoundPaddingLeft;
5334 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005335 }
5336
Gilles Debunne8615ac92011-11-29 15:25:03 -08005337 invalidate(mScrollX + left, verticalPadding + top,
5338 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005339 }
5340 }
5341
5342 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005343 if (!mPreDrawRegistered) {
5344 getViewTreeObserver().addOnPreDrawListener(this);
5345 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005346 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005347 }
5348
Craig Stoutf209ef92014-07-02 15:23:16 -07005349 private void unregisterForPreDraw() {
5350 getViewTreeObserver().removeOnPreDrawListener(this);
5351 mPreDrawRegistered = false;
5352 mPreDrawListenerDetached = false;
5353 }
5354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005355 /**
5356 * {@inheritDoc}
5357 */
5358 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005359 if (mLayout == null) {
5360 assumeLayout();
5361 }
5362
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005363 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07005364 /* This code also provides auto-scrolling when a cursor is moved using a
5365 * CursorController (insertion point or selection limits).
5366 * For selection, ensure start or end is visible depending on controller's state.
5367 */
5368 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08005369 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005370 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5371 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005372 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07005373 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005374
5375 /*
5376 * TODO: This should really only keep the end in view if
5377 * it already was before the text changed. I'm not sure
5378 * of a good way to tell from here if it was.
5379 */
Gilles Debunne60e21862012-01-30 15:04:14 -08005380 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005381 curs = mText.length();
5382 }
5383
5384 if (curs >= 0) {
Raph Leviene048f842013-09-27 13:36:24 -07005385 bringPointIntoView(curs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005386 }
5387 } else {
Raph Leviene048f842013-09-27 13:36:24 -07005388 bringTextIntoView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005389 }
5390
Gilles Debunne64e54a62010-09-07 19:07:17 -07005391 // This has to be checked here since:
5392 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5393 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005394 if (mEditor != null && mEditor.mCreatedWithASelection) {
Andrei Stingaceanu99d3bbd2015-06-30 16:57:03 +01005395 if (mEditor.extractedTextModeWillBeStarted()) {
5396 mEditor.checkFieldAndSelectCurrentWord();
5397 } else {
5398 mEditor.startSelectionActionMode();
5399 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005400 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08005401 }
5402
Craig Stoutf209ef92014-07-02 15:23:16 -07005403 unregisterForPreDraw();
Gilles Debunne2e37d622012-01-27 13:54:00 -08005404
Raph Leviene048f842013-09-27 13:36:24 -07005405 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005406 }
5407
5408 @Override
5409 protected void onAttachedToWindow() {
5410 super.onAttachedToWindow();
5411
5412 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005413
Gilles Debunne2d373a12012-04-20 15:32:19 -07005414 if (mEditor != null) mEditor.onAttachedToWindow();
Craig Stoutf209ef92014-07-02 15:23:16 -07005415
5416 if (mPreDrawListenerDetached) {
5417 getViewTreeObserver().addOnPreDrawListener(this);
5418 mPreDrawListenerDetached = false;
5419 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005420 }
5421
John Reckb14dfe22014-03-06 22:06:20 +00005422 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005423 @Override
John Reckb14dfe22014-03-06 22:06:20 +00005424 protected void onDetachedFromWindowInternal() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005425 if (mPreDrawRegistered) {
5426 getViewTreeObserver().removeOnPreDrawListener(this);
Craig Stoutf209ef92014-07-02 15:23:16 -07005427 mPreDrawListenerDetached = true;
Gilles Debunne81f08082011-02-17 14:07:19 -08005428 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005429
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005430 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07005431
Gilles Debunne2d373a12012-04-20 15:32:19 -07005432 if (mEditor != null) mEditor.onDetachedFromWindow();
John Reckb14dfe22014-03-06 22:06:20 +00005433
5434 super.onDetachedFromWindowInternal();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005435 }
5436
5437 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08005438 public void onScreenStateChanged(int screenState) {
5439 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005440 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08005441 }
5442
5443 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005444 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005445 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005446 }
5447
5448 @Override
5449 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005450 return getCompoundPaddingLeft() - mPaddingLeft +
5451 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005452 }
5453
5454 @Override
5455 protected int getTopPaddingOffset() {
5456 return (int) Math.min(0, mShadowDy - mShadowRadius);
5457 }
5458
5459 @Override
5460 protected int getBottomPaddingOffset() {
5461 return (int) Math.max(0, mShadowDy + mShadowRadius);
5462 }
5463
5464 @Override
5465 protected int getRightPaddingOffset() {
Siyamed Sinir217c0f72016-02-01 18:30:02 -08005466 return -(getCompoundPaddingRight() - mPaddingRight) +
Romain Guy076dc9f2009-06-24 17:17:51 -07005467 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005468 }
5469
5470 @Override
5471 protected boolean verifyDrawable(Drawable who) {
5472 final boolean verified = super.verifyDrawable(who);
5473 if (!verified && mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005474 for (Drawable dr : mDrawables.mShowing) {
5475 if (who == dr) {
5476 return true;
5477 }
5478 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005479 }
5480 return verified;
5481 }
5482
5483 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07005484 public void jumpDrawablesToCurrentState() {
5485 super.jumpDrawablesToCurrentState();
5486 if (mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005487 for (Drawable dr : mDrawables.mShowing) {
5488 if (dr != null) {
5489 dr.jumpToCurrentState();
5490 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005491 }
Dianne Hackborne2136772010-11-04 15:08:59 -07005492 }
5493 }
5494
5495 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07005496 public void invalidateDrawable(Drawable drawable) {
Alan Viverettee6875f12014-02-05 14:05:17 -08005497 boolean handled = false;
5498
Romain Guy3c77d392009-05-20 11:26:50 -07005499 if (verifyDrawable(drawable)) {
5500 final Rect dirty = drawable.getBounds();
5501 int scrollX = mScrollX;
5502 int scrollY = mScrollY;
5503
5504 // IMPORTANT: The coordinates below are based on the coordinates computed
5505 // for each compound drawable in onDraw(). Make sure to update each section
5506 // accordingly.
5507 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07005508 if (drawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005509 if (drawable == drawables.mShowing[Drawables.LEFT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005510 final int compoundPaddingTop = getCompoundPaddingTop();
5511 final int compoundPaddingBottom = getCompoundPaddingBottom();
5512 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005513
Romain Guya6cd4e02009-05-20 15:09:21 -07005514 scrollX += mPaddingLeft;
5515 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005516 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005517 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005518 final int compoundPaddingTop = getCompoundPaddingTop();
5519 final int compoundPaddingBottom = getCompoundPaddingBottom();
5520 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005521
Romain Guya6cd4e02009-05-20 15:09:21 -07005522 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5523 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005524 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005525 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005526 final int compoundPaddingLeft = getCompoundPaddingLeft();
5527 final int compoundPaddingRight = getCompoundPaddingRight();
5528 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005529
Romain Guya6cd4e02009-05-20 15:09:21 -07005530 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5531 scrollY += mPaddingTop;
Alan Viverettee6875f12014-02-05 14:05:17 -08005532 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005533 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005534 final int compoundPaddingLeft = getCompoundPaddingLeft();
5535 final int compoundPaddingRight = getCompoundPaddingRight();
5536 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005537
Romain Guya6cd4e02009-05-20 15:09:21 -07005538 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5539 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
Alan Viverettee6875f12014-02-05 14:05:17 -08005540 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07005541 }
Romain Guy3c77d392009-05-20 11:26:50 -07005542 }
5543
Alan Viverettee6875f12014-02-05 14:05:17 -08005544 if (handled) {
5545 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5546 dirty.right + scrollX, dirty.bottom + scrollY);
5547 }
5548 }
5549
5550 if (!handled) {
5551 super.invalidateDrawable(drawable);
Romain Guy3c77d392009-05-20 11:26:50 -07005552 }
5553 }
5554
5555 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07005556 public boolean hasOverlappingRendering() {
Chris Craik7bcde502013-10-11 12:51:11 -07005557 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
Michael Jurka0931a852013-03-21 16:07:45 +01005558 return ((getBackground() != null && getBackground().getCurrent() != null)
Chris Craik7bcde502013-10-11 12:51:11 -07005559 || mText instanceof Spannable || hasSelection()
5560 || isHorizontalFadingEdgeEnabled());
Chet Haasedb8c9a62012-03-21 18:54:18 -07005561 }
5562
Gilles Debunne86b9c782010-11-11 10:43:48 -08005563 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08005564 *
Joe Malin10d96952013-05-29 17:49:09 -07005565 * Returns the state of the {@code textIsSelectable} flag (See
5566 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5567 * to allow users to select and copy text in a non-editable TextView, the content of an
5568 * {@link EditText} can always be selected, independently of the value of this flag.
5569 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08005570 *
5571 * @return True if the text displayed in this TextView can be selected by the user.
5572 *
5573 * @attr ref android.R.styleable#TextView_textIsSelectable
5574 */
5575 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005576 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005577 }
5578
5579 /**
Joe Malin10d96952013-05-29 17:49:09 -07005580 * Sets whether the content of this view is selectable by the user. The default is
5581 * {@code false}, meaning that the content is not selectable.
5582 * <p>
5583 * When you use a TextView to display a useful piece of information to the user (such as a
5584 * contact's address), make it selectable, so that the user can select and copy its
5585 * content. You can also use set the XML attribute
5586 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5587 * <p>
5588 * When you call this method to set the value of {@code textIsSelectable}, it sets
5589 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5590 * and {@code longClickable} to the same value. These flags correspond to the attributes
5591 * {@link android.R.styleable#View_focusable android:focusable},
5592 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5593 * {@link android.R.styleable#View_clickable android:clickable}, and
5594 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5595 * flags to a state you had set previously, call one or more of the following methods:
5596 * {@link #setFocusable(boolean) setFocusable()},
5597 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5598 * {@link #setClickable(boolean) setClickable()} or
5599 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08005600 *
Joe Malin10d96952013-05-29 17:49:09 -07005601 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08005602 */
5603 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005604 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08005605
Gilles Debunne5fae9962012-05-08 14:53:20 -07005606 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005607 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005608
Gilles Debunne2d373a12012-04-20 15:32:19 -07005609 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08005610 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005611 setFocusable(selectable);
5612 setClickable(selectable);
5613 setLongClickable(selectable);
5614
Gilles Debunne60e21862012-01-30 15:04:14 -08005615 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08005616
5617 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07005618 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005619
5620 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07005621 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08005622 }
5623
5624 @Override
5625 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08005626 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005627
Gilles Debunnefb817032011-01-13 13:52:49 -08005628 if (mSingleLine) {
5629 drawableState = super.onCreateDrawableState(extraSpace);
5630 } else {
5631 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005632 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5633 }
5634
Gilles Debunne60e21862012-01-30 15:04:14 -08005635 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08005636 // Disable pressed state, which was introduced when TextView was made clickable.
5637 // Prevents text color change.
5638 // setClickable(false) would have a similar effect, but it also disables focus changes
5639 // and long press actions, which are both needed by text selection.
5640 final int length = drawableState.length;
5641 for (int i = 0; i < length; i++) {
5642 if (drawableState[i] == R.attr.state_pressed) {
5643 final int[] nonPressedState = new int[length - 1];
5644 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5645 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5646 return nonPressedState;
5647 }
5648 }
5649 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005650
Gilles Debunne86b9c782010-11-11 10:43:48 -08005651 return drawableState;
5652 }
5653
Gilles Debunne83051b82012-02-24 20:01:13 -08005654 private Path getUpdatedHighlightPath() {
5655 Path highlight = null;
5656 Paint highlightPaint = mHighlightPaint;
5657
5658 final int selStart = getSelectionStart();
5659 final int selEnd = getSelectionEnd();
5660 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5661 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005662 if (mEditor != null && mEditor.isCursorVisible() &&
5663 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07005664 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08005665 if (mHighlightPathBogus) {
5666 if (mHighlightPath == null) mHighlightPath = new Path();
5667 mHighlightPath.reset();
5668 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005669 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08005670 mHighlightPathBogus = false;
5671 }
5672
5673 // XXX should pass to skin instead of drawing directly
5674 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005675 highlightPaint.setStyle(Paint.Style.STROKE);
5676 highlight = mHighlightPath;
5677 }
5678 } else {
5679 if (mHighlightPathBogus) {
5680 if (mHighlightPath == null) mHighlightPath = new Path();
5681 mHighlightPath.reset();
5682 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5683 mHighlightPathBogus = false;
5684 }
5685
5686 // XXX should pass to skin instead of drawing directly
5687 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005688 highlightPaint.setStyle(Paint.Style.FILL);
5689
5690 highlight = mHighlightPath;
5691 }
5692 }
5693 return highlight;
5694 }
5695
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005696 /**
5697 * @hide
5698 */
5699 public int getHorizontalOffsetForDrawables() {
5700 return 0;
5701 }
5702
Romain Guyc4d8eb62010-08-18 20:48:33 -07005703 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005704 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005705 restartMarqueeIfNeeded();
5706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005707 // Draw the background for this view
5708 super.onDraw(canvas);
5709
5710 final int compoundPaddingLeft = getCompoundPaddingLeft();
5711 final int compoundPaddingTop = getCompoundPaddingTop();
5712 final int compoundPaddingRight = getCompoundPaddingRight();
5713 final int compoundPaddingBottom = getCompoundPaddingBottom();
5714 final int scrollX = mScrollX;
5715 final int scrollY = mScrollY;
5716 final int right = mRight;
5717 final int left = mLeft;
5718 final int bottom = mBottom;
5719 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005720 final boolean isLayoutRtl = isLayoutRtl();
5721 final int offset = getHorizontalOffsetForDrawables();
5722 final int leftOffset = isLayoutRtl ? 0 : offset;
5723 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005724
5725 final Drawables dr = mDrawables;
5726 if (dr != null) {
5727 /*
5728 * Compound, not extended, because the icon is not clipped
5729 * if the text height is smaller.
5730 */
5731
5732 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5733 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5734
Romain Guy3c77d392009-05-20 11:26:50 -07005735 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5736 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005737 if (dr.mShowing[Drawables.LEFT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005738 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005739 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005740 scrollY + compoundPaddingTop +
5741 (vspace - dr.mDrawableHeightLeft) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005742 dr.mShowing[Drawables.LEFT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005743 canvas.restore();
5744 }
5745
Romain Guy3c77d392009-05-20 11:26:50 -07005746 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5747 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005748 if (dr.mShowing[Drawables.RIGHT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005749 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005750 canvas.translate(scrollX + right - left - mPaddingRight
5751 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005752 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005753 dr.mShowing[Drawables.RIGHT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005754 canvas.restore();
5755 }
5756
Romain Guy3c77d392009-05-20 11:26:50 -07005757 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5758 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005759 if (dr.mShowing[Drawables.TOP] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005760 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005761 canvas.translate(scrollX + compoundPaddingLeft +
5762 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005763 dr.mShowing[Drawables.TOP].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005764 canvas.restore();
5765 }
5766
Romain Guy3c77d392009-05-20 11:26:50 -07005767 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5768 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005769 if (dr.mShowing[Drawables.BOTTOM] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005770 canvas.save();
5771 canvas.translate(scrollX + compoundPaddingLeft +
5772 (hspace - dr.mDrawableWidthBottom) / 2,
5773 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005774 dr.mShowing[Drawables.BOTTOM].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005775 canvas.restore();
5776 }
5777 }
5778
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005779 int color = mCurTextColor;
5780
5781 if (mLayout == null) {
5782 assumeLayout();
5783 }
5784
5785 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005786
5787 if (mHint != null && mText.length() == 0) {
5788 if (mHintTextColor != null) {
5789 color = mCurHintTextColor;
5790 }
5791
5792 layout = mHintLayout;
5793 }
5794
5795 mTextPaint.setColor(color);
5796 mTextPaint.drawableState = getDrawableState();
5797
5798 canvas.save();
5799 /* Would be faster if we didn't have to do this. Can we chop the
5800 (displayable) text so that we don't need to do this ever?
5801 */
5802
5803 int extendedPaddingTop = getExtendedPaddingTop();
5804 int extendedPaddingBottom = getExtendedPaddingBottom();
5805
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005806 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5807 final int maxScrollY = mLayout.getHeight() - vspace;
5808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005809 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005810 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
Siyamed Sinir217c0f72016-02-01 18:30:02 -08005811 float clipRight = right - left - getCompoundPaddingRight() + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005812 float clipBottom = bottom - top + scrollY -
5813 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005814
5815 if (mShadowRadius != 0) {
5816 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5817 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5818
5819 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5820 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5821 }
5822
5823 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5824
5825 int voffsetText = 0;
5826 int voffsetCursor = 0;
5827
5828 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005829 /* shortcircuit calling getVerticaOffset() */
5830 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5831 voffsetText = getVerticalOffset(false);
5832 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005833 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005834 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005835
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005836 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005837 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005838 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5839 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005840 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005841 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005842 final int width = mRight - mLeft;
5843 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5844 final float dx = mLayout.getLineRight(0) - (width - padding);
Michael Lekman89bc4132012-01-11 14:27:52 +01005845 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005846 }
5847
5848 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005849 final float dx = -mMarquee.getScroll();
Michael Lekman89bc4132012-01-11 14:27:52 +01005850 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005851 }
5852 }
5853
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005854 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005855
Gilles Debunne83051b82012-02-24 20:01:13 -08005856 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005857 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005858 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005859 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005860 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005861 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005862
Gilles Debunned88876a2012-03-16 17:34:04 -07005863 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Michael Lekman89bc4132012-01-11 14:27:52 +01005864 final float dx = mMarquee.getGhostOffset();
5865 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005866 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005867 }
5868
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005869 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005870 }
5871
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005872 @Override
5873 public void getFocusedRect(Rect r) {
5874 if (mLayout == null) {
5875 super.getFocusedRect(r);
5876 return;
5877 }
5878
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005879 int selEnd = getSelectionEnd();
5880 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005881 super.getFocusedRect(r);
5882 return;
5883 }
5884
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005885 int selStart = getSelectionStart();
5886 if (selStart < 0 || selStart >= selEnd) {
5887 int line = mLayout.getLineForOffset(selEnd);
5888 r.top = mLayout.getLineTop(line);
5889 r.bottom = mLayout.getLineBottom(line);
5890 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5891 r.right = r.left + 4;
5892 } else {
5893 int lineStart = mLayout.getLineForOffset(selStart);
5894 int lineEnd = mLayout.getLineForOffset(selEnd);
5895 r.top = mLayout.getLineTop(lineStart);
5896 r.bottom = mLayout.getLineBottom(lineEnd);
5897 if (lineStart == lineEnd) {
5898 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5899 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5900 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005901 // Selection extends across multiple lines -- make the focused
5902 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005903 if (mHighlightPathBogus) {
5904 if (mHighlightPath == null) mHighlightPath = new Path();
5905 mHighlightPath.reset();
5906 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5907 mHighlightPathBogus = false;
5908 }
5909 synchronized (TEMP_RECTF) {
5910 mHighlightPath.computeBounds(TEMP_RECTF, true);
5911 r.left = (int)TEMP_RECTF.left-1;
5912 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005913 }
5914 }
5915 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005916
5917 // Adjust for padding and gravity.
5918 int paddingLeft = getCompoundPaddingLeft();
5919 int paddingTop = getExtendedPaddingTop();
5920 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5921 paddingTop += getVerticalOffset(false);
5922 }
5923 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005924 int paddingBottom = getExtendedPaddingBottom();
5925 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005926 }
5927
5928 /**
5929 * Return the number of lines of text, or 0 if the internal Layout has not
5930 * been built.
5931 */
5932 public int getLineCount() {
5933 return mLayout != null ? mLayout.getLineCount() : 0;
5934 }
5935
5936 /**
5937 * Return the baseline for the specified line (0...getLineCount() - 1)
5938 * If bounds is not null, return the top, left, right, bottom extents
5939 * of the specified line in it. If the internal Layout has not been built,
5940 * return 0 and set bounds to (0, 0, 0, 0)
5941 * @param line which line to examine (0..getLineCount() - 1)
5942 * @param bounds Optional. If not null, it returns the extent of the line
5943 * @return the Y-coordinate of the baseline
5944 */
5945 public int getLineBounds(int line, Rect bounds) {
5946 if (mLayout == null) {
5947 if (bounds != null) {
5948 bounds.set(0, 0, 0, 0);
5949 }
5950 return 0;
5951 }
5952 else {
5953 int baseline = mLayout.getLineBounds(line, bounds);
5954
5955 int voffset = getExtendedPaddingTop();
5956 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5957 voffset += getVerticalOffset(true);
5958 }
5959 if (bounds != null) {
5960 bounds.offset(getCompoundPaddingLeft(), voffset);
5961 }
5962 return baseline + voffset;
5963 }
5964 }
5965
5966 @Override
5967 public int getBaseline() {
5968 if (mLayout == null) {
5969 return super.getBaseline();
5970 }
5971
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07005972 return getBaselineOffset() + mLayout.getLineBaseline(0);
5973 }
5974
5975 int getBaselineOffset() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005976 int voffset = 0;
5977 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5978 voffset = getVerticalOffset(true);
5979 }
5980
Philip Milne7b757812012-09-19 18:13:44 -07005981 if (isLayoutModeOptical(mParent)) {
5982 voffset -= getOpticalInsets().top;
5983 }
5984
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07005985 return getExtendedPaddingTop() + voffset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005986 }
5987
Romain Guyf2fc4602011-07-19 15:20:03 -07005988 /**
5989 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005990 */
5991 @Override
5992 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005993 if (mLayout == null) return 0;
5994
Romain Guyf2fc4602011-07-19 15:20:03 -07005995 int voffset = 0;
5996 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5997 voffset = getVerticalOffset(true);
5998 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005999
Romain Guyf2fc4602011-07-19 15:20:03 -07006000 if (offsetRequired) voffset += getTopPaddingOffset();
6001
6002 return getExtendedPaddingTop() + voffset;
6003 }
6004
6005 /**
6006 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07006007 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07006008 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07006009 protected int getFadeHeight(boolean offsetRequired) {
6010 return mLayout != null ? mLayout.getHeight() : 0;
6011 }
6012
Jun Mukai1db53972015-09-11 18:08:31 -07006013 @Override
Jun Mukaid4eaef72015-10-30 15:54:33 -07006014 public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
Keisuke Kuroyanagi366a8862015-12-15 16:34:35 +09006015 if (mText instanceof Spannable && mLinksClickable) {
6016 final int offset = getOffsetForPosition(x, y);
6017 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6018 ClickableSpan.class);
6019 if (clickables.length > 0) {
6020 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_HAND);
6021 }
6022 }
Jun Mukai1db53972015-09-11 18:08:31 -07006023 if (isTextSelectable() || isTextEditable()) {
Jun Mukaid4eaef72015-10-30 15:54:33 -07006024 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_TEXT);
Jun Mukai1db53972015-09-11 18:08:31 -07006025 }
Jun Mukaid4eaef72015-10-30 15:54:33 -07006026 return super.getPointerIcon(event, x, y);
Jun Mukai1db53972015-09-11 18:08:31 -07006027 }
6028
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006029 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006030 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
Yohei Yukawa38940aa2015-06-24 00:20:24 -07006031 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6032 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6033 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6034 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6035 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006036 }
6037 return super.onKeyPreIme(keyCode, event);
6038 }
6039
Yohei Yukawa38940aa2015-06-24 00:20:24 -07006040 /**
6041 * @hide
6042 */
6043 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6044 // Do nothing unless mEditor is in text action mode.
6045 if (mEditor == null || mEditor.mTextActionMode == null) {
6046 return false;
6047 }
6048
6049 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6050 KeyEvent.DispatcherState state = getKeyDispatcherState();
6051 if (state != null) {
6052 state.startTracking(event, this);
6053 }
6054 return true;
6055 } else if (event.getAction() == KeyEvent.ACTION_UP) {
6056 KeyEvent.DispatcherState state = getKeyDispatcherState();
6057 if (state != null) {
6058 state.handleUpEvent(event);
6059 }
6060 if (event.isTracking() && !event.isCanceled()) {
6061 stopTextActionMode();
6062 return true;
6063 }
6064 }
6065 return false;
6066 }
6067
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006068 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006069 public boolean onKeyDown(int keyCode, KeyEvent event) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006070 final int which = doKeyDown(keyCode, event, null);
6071 if (which == KEY_EVENT_NOT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006072 return super.onKeyDown(keyCode, event);
6073 }
6074
6075 return true;
6076 }
6077
6078 @Override
6079 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006080 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006081 final int which = doKeyDown(keyCode, down, event);
6082 if (which == KEY_EVENT_NOT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006083 // Go through default dispatching.
6084 return super.onKeyMultiple(keyCode, repeatCount, event);
6085 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006086 if (which == KEY_EVENT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006087 // Consumed the whole thing.
6088 return true;
6089 }
6090
6091 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006093 // We are going to dispatch the remaining events to either the input
6094 // or movement method. To do this, we will just send a repeated stream
6095 // of down and up events until we have done the complete repeatCount.
6096 // It would be nice if those interfaces had an onKeyMultiple() method,
6097 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07006098 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006099 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006100 // mEditor and mEditor.mInput are not null from doKeyDown
6101 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006102 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006103 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6104 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006105 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006106 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006107
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006108 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
Gilles Debunne60e21862012-01-30 15:04:14 -08006109 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006110 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6111 while (--repeatCount > 0) {
6112 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6113 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6114 }
6115 }
6116
6117 return true;
6118 }
6119
6120 /**
6121 * Returns true if pressing ENTER in this field advances focus instead
6122 * of inserting the character. This is true mostly in single-line fields,
6123 * but also in mail addresses and subjects which will display on multiple
6124 * lines but where it doesn't make sense to insert newlines.
6125 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08006126 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08006127 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006128 return false;
6129 }
6130
6131 if (mSingleLine) {
6132 return true;
6133 }
6134
Gilles Debunne2d373a12012-04-20 15:32:19 -07006135 if (mEditor != null &&
6136 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6137 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006138 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6139 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006140 return true;
6141 }
6142 }
6143
6144 return false;
6145 }
6146
Jeff Brown4e6319b2010-12-13 10:36:51 -08006147 /**
6148 * Returns true if pressing TAB in this field advances focus instead
6149 * of inserting the character. Insert tabs only in multi-line editors.
6150 */
6151 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006152 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6153 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6154 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6155 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6156 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6157 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006158 }
6159 }
6160 return true;
6161 }
6162
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006163 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6164 if (!isEnabled()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006165 return KEY_EVENT_NOT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006166 }
6167
Michael Wright3a7e4832013-02-11 15:55:50 -08006168 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6169 // While this shouldn't be necessary because any time we're preventing default movement we
6170 // should be restricting the focus to remain within this view, thus we'll also receive
6171 // the key up event, occasionally key up events will get dropped and we don't want to
6172 // prevent the user from traversing out of this on the next key down.
6173 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6174 mPreventDefaultMovement = false;
6175 }
6176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006177 switch (keyCode) {
6178 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006179 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006180 // When mInputContentType is set, we know that we are
6181 // running in a "modern" cupcake environment, so don't need
6182 // to worry about the application trying to capture
6183 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006184 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006185 // If there is an action listener, given them a
6186 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006187 if (mEditor.mInputContentType.onEditorActionListener != null &&
6188 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07006189 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006190 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07006191 // We are consuming the enter key for them.
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006192 return KEY_EVENT_HANDLED;
The Android Open Source Project10592532009-03-18 17:39:46 -07006193 }
6194 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006195
The Android Open Source Project10592532009-03-18 17:39:46 -07006196 // If our editor should move focus when enter is pressed, or
6197 // this is a generated event from an IME action button, then
6198 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08006199 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07006200 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07006201 if (hasOnClickListeners()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006202 return KEY_EVENT_NOT_HANDLED;
Leon Scroggins7014b122011-01-11 15:17:34 -05006203 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006204 return KEY_EVENT_HANDLED;
The Android Open Source Project10592532009-03-18 17:39:46 -07006205 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006206 }
The Android Open Source Project10592532009-03-18 17:39:46 -07006207 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006208
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006209 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006210 if (event.hasNoModifiers()) {
6211 if (shouldAdvanceFocusOnEnter()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006212 return KEY_EVENT_NOT_HANDLED;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006213 }
6214 }
6215 break;
6216
6217 case KeyEvent.KEYCODE_TAB:
6218 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6219 if (shouldAdvanceFocusOnTab()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006220 return KEY_EVENT_NOT_HANDLED;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006221 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006222 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006223 break;
6224
6225 // Has to be done on key down (and not on key up) to correctly be intercepted.
6226 case KeyEvent.KEYCODE_BACK:
Clara Bayarri7938cdb2015-06-02 20:03:45 +01006227 if (mEditor != null && mEditor.mTextActionMode != null) {
6228 stopTextActionMode();
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006229 return KEY_EVENT_HANDLED;
Gilles Debunne64e54a62010-09-07 19:07:17 -07006230 }
6231 break;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006232
6233 case KeyEvent.KEYCODE_CUT:
6234 if (event.hasNoModifiers() && canCut()) {
6235 if (onTextContextMenuItem(ID_CUT)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006236 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006237 }
6238 }
6239 break;
6240
6241 case KeyEvent.KEYCODE_COPY:
6242 if (event.hasNoModifiers() && canCopy()) {
6243 if (onTextContextMenuItem(ID_COPY)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006244 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006245 }
6246 }
6247 break;
6248
6249 case KeyEvent.KEYCODE_PASTE:
6250 if (event.hasNoModifiers() && canPaste()) {
6251 if (onTextContextMenuItem(ID_PASTE)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006252 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006253 }
6254 }
6255 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006256 }
6257
Gilles Debunne2d373a12012-04-20 15:32:19 -07006258 if (mEditor != null && mEditor.mKeyListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006259 boolean doDown = true;
6260 if (otherEvent != null) {
6261 try {
6262 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006263 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6264 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006265 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006266 doDown = false;
6267 if (handled) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006268 return KEY_EVENT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006269 }
6270 } catch (AbstractMethodError e) {
6271 // onKeyOther was added after 1.0, so if it isn't
6272 // implemented we need to try to dispatch as a regular down.
6273 } finally {
6274 endBatchEdit();
6275 }
6276 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006278 if (doDown) {
6279 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006280 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6281 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006282 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08006283 hideErrorIfUnchanged();
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006284 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006285 }
6286 }
6287
6288 // bug 650865: sometimes we get a key event before a layout.
6289 // don't try to move around if we don't know the layout.
6290
6291 if (mMovement != null && mLayout != null) {
6292 boolean doDown = true;
6293 if (otherEvent != null) {
6294 try {
6295 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6296 otherEvent);
6297 doDown = false;
6298 if (handled) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006299 return KEY_EVENT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006300 }
6301 } catch (AbstractMethodError e) {
6302 // onKeyOther was added after 1.0, so if it isn't
6303 // implemented we need to try to dispatch as a regular down.
6304 }
6305 }
6306 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08006307 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6308 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6309 mPreventDefaultMovement = true;
6310 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006311 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
Michael Wright3a7e4832013-02-11 15:55:50 -08006312 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006313 }
6314 }
6315
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006316 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6317 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006318 }
6319
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006320 /**
6321 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6322 * can be recorded.
6323 * @hide
6324 */
6325 public void resetErrorChangedFlag() {
6326 /*
6327 * Keep track of what the error was before doing the input
6328 * so that if an input filter changed the error, we leave
6329 * that error showing. Otherwise, we take down whatever
6330 * error was showing when the user types something.
6331 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07006332 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006333 }
6334
6335 /**
6336 * @hide
6337 */
6338 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006339 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006340 setError(null, null);
6341 }
6342 }
6343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006344 @Override
6345 public boolean onKeyUp(int keyCode, KeyEvent event) {
6346 if (!isEnabled()) {
6347 return super.onKeyUp(keyCode, event);
6348 }
6349
Michael Wright3a7e4832013-02-11 15:55:50 -08006350 if (!KeyEvent.isModifierKey(keyCode)) {
6351 mPreventDefaultMovement = false;
6352 }
6353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006354 switch (keyCode) {
6355 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006356 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006357 /*
6358 * If there is a click listener, just call through to
6359 * super, which will invoke it.
6360 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08006361 * If there isn't a click listener, try to show the soft
6362 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006363 * call performClick(), but that won't do anything in
6364 * this case.)
6365 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006366 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006367 if (mMovement != null && mText instanceof Editable
6368 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08006369 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09006370 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07006371 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09006372 imm.showSoftInput(this, 0);
6373 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006374 }
6375 }
6376 }
6377 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006378
Jeff Brown4e6319b2010-12-13 10:36:51 -08006379 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006380 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006381 if (mEditor != null && mEditor.mInputContentType != null
6382 && mEditor.mInputContentType.onEditorActionListener != null
6383 && mEditor.mInputContentType.enterDown) {
6384 mEditor.mInputContentType.enterDown = false;
6385 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08006386 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006387 return true;
6388 }
6389 }
6390
Jeff Brown4e6319b2010-12-13 10:36:51 -08006391 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6392 || shouldAdvanceFocusOnEnter()) {
6393 /*
6394 * If there is a click listener, just call through to
6395 * super, which will invoke it.
6396 *
6397 * If there isn't a click listener, try to advance focus,
6398 * but still call through to super, which will reset the
6399 * pressed state and longpress state. (It will also
6400 * call performClick(), but that won't do anything in
6401 * this case.)
6402 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006403 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006404 View v = focusSearch(FOCUS_DOWN);
6405
6406 if (v != null) {
6407 if (!v.requestFocus(FOCUS_DOWN)) {
6408 throw new IllegalStateException(
6409 "focus search returned a view " +
6410 "that wasn't able to take focus!");
6411 }
6412
6413 /*
6414 * Return true because we handled the key; super
6415 * will return false because there was no click
6416 * listener.
6417 */
6418 super.onKeyUp(keyCode, event);
6419 return true;
6420 } else if ((event.getFlags()
6421 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6422 // No target for next focus, but make sure the IME
6423 // if this came from it.
6424 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08006425 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006426 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6427 }
6428 }
6429 }
6430 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006431 return super.onKeyUp(keyCode, event);
6432 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006433 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006434 }
6435
Gilles Debunne2d373a12012-04-20 15:32:19 -07006436 if (mEditor != null && mEditor.mKeyListener != null)
6437 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006438 return true;
6439
6440 if (mMovement != null && mLayout != null)
6441 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6442 return true;
6443
6444 return super.onKeyUp(keyCode, event);
6445 }
6446
Gilles Debunnec1714022012-01-17 13:59:23 -08006447 @Override
6448 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006449 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006450 }
Gilles Debunneb062e812011-09-27 14:58:37 -07006451
Gilles Debunnec1714022012-01-17 13:59:23 -08006452 @Override
6453 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03006454 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006455 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08006456 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006457 if (mEditor.mInputContentType != null) {
6458 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6459 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6460 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6461 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6462 outAttrs.extras = mEditor.mInputContentType.extras;
Yohei Yukawad469f212016-01-21 12:38:09 -08006463 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006464 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006465 outAttrs.imeOptions = EditorInfo.IME_NULL;
Yohei Yukawad469f212016-01-21 12:38:09 -08006466 outAttrs.hintLocales = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006467 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006468 if (focusSearch(FOCUS_DOWN) != null) {
6469 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6470 }
6471 if (focusSearch(FOCUS_UP) != null) {
6472 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6473 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006474 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6475 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006476 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006477 // An action has not been set, but the enter key will move to
6478 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006479 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006480 } else {
6481 // An action has not been set, and there is no focus to move
6482 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006483 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006484 }
6485 if (!shouldAdvanceFocusOnEnter()) {
6486 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006487 }
6488 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006489 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006490 // Multi-line text editors should always show an enter key.
6491 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6492 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006493 outAttrs.hintText = mHint;
6494 if (mText instanceof Editable) {
6495 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07006496 outAttrs.initialSelStart = getSelectionStart();
6497 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08006498 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006499 return ic;
6500 }
6501 }
6502 return null;
6503 }
6504
6505 /**
6506 * If this TextView contains editable content, extract a portion of it
6507 * based on the information in <var>request</var> in to <var>outText</var>.
6508 * @return Returns true if the text was successfully extracted, else false.
6509 */
Gilles Debunned88876a2012-03-16 17:34:04 -07006510 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07006511 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006512 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006513 }
Viktor Yakovel964be412010-02-17 08:35:57 +01006514
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006515 /**
6516 * This is used to remove all style-impacting spans from text before new
6517 * extracted text is being replaced into it, so that we don't have any
6518 * lingering spans applied during the replace.
6519 */
6520 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6521 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6522 int i = spans.length;
6523 while (i > 0) {
6524 i--;
6525 spannable.removeSpan(spans[i]);
6526 }
6527 }
Gilles Debunned88876a2012-03-16 17:34:04 -07006528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006529 /**
6530 * Apply to this text view the given extracted text, as previously
6531 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6532 */
6533 public void setExtractedText(ExtractedText text) {
6534 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006535 if (text.text != null) {
6536 if (content == null) {
6537 setText(text.text, TextView.BufferType.EDITABLE);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006538 } else {
Andrei Stingaceanucfa13a72015-07-08 15:07:59 +01006539 int start = 0;
6540 int end = content.length();
6541
6542 if (text.partialStartOffset >= 0) {
6543 final int N = content.length();
6544 start = text.partialStartOffset;
6545 if (start > N) start = N;
6546 end = text.partialEndOffset;
6547 if (end > N) end = N;
6548 }
6549
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006550 removeParcelableSpans(content, start, end);
Andrei Stingaceanucfa13a72015-07-08 15:07:59 +01006551 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6552 if (text.text instanceof Spanned) {
6553 // OK to copy spans only.
6554 TextUtils.copySpansFrom((Spanned) text.text, start, end,
6555 Object.class, content, start);
6556 }
6557 } else {
6558 content.replace(start, end, text.text);
6559 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006560 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006561 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006562
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006563 // Now set the selection position... make sure it is in range, to
6564 // avoid crashes. If this is a partial update, it is possible that
6565 // the underlying text may have changed, causing us problems here.
6566 // Also we just don't want to trust clients to do the right thing.
6567 Spannable sp = (Spannable)getText();
6568 final int N = sp.length();
6569 int start = text.selectionStart;
6570 if (start < 0) start = 0;
6571 else if (start > N) start = N;
6572 int end = text.selectionEnd;
6573 if (end < 0) end = 0;
6574 else if (end > N) end = N;
6575 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006576
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006577 // Finally, update the selection mode.
6578 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6579 MetaKeyKeyListener.startSelecting(this, sp);
6580 } else {
6581 MetaKeyKeyListener.stopSelecting(this, sp);
6582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006583 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006585 /**
6586 * @hide
6587 */
6588 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006589 if (mEditor.mInputMethodState != null) {
6590 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006591 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006592 // This would stop a possible selection mode, but no such mode is started in case
6593 // extracted mode will start. Some text is selected though, and will trigger an action mode
6594 // in the extracted view.
Mady Mellora2861452015-06-25 08:40:27 -07006595 mEditor.hideCursorAndSpanControllers();
Clara Bayarri7938cdb2015-06-02 20:03:45 +01006596 stopTextActionMode();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006597 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006599 /**
6600 * Called by the framework in response to a text completion from
6601 * the current input method, provided by it calling
6602 * {@link InputConnection#commitCompletion
6603 * InputConnection.commitCompletion()}. The default implementation does
6604 * nothing; text views that are supporting auto-completion should override
6605 * this to do their desired behavior.
6606 *
6607 * @param text The auto complete text the user has selected.
6608 */
6609 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006610 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006611 }
6612
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006613 /**
6614 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6615 * a dictionnary) from the current input method, provided by it calling
6616 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6617 * implementation flashes the background of the corrected word to provide feedback to the user.
6618 *
6619 * @param info The auto correct info about the text that was corrected.
6620 */
6621 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006622 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006623 }
6624
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006625 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006626 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006627 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006628
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006629 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006630 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006631 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006632
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006633 /**
6634 * Called by the framework in response to a request to begin a batch
6635 * of edit operations through a call to link {@link #beginBatchEdit()}.
6636 */
6637 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006638 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006639 }
Gilles Debunne60e21862012-01-30 15:04:14 -08006640
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006641 /**
6642 * Called by the framework in response to a request to end a batch
6643 * of edit operations through a call to link {@link #endBatchEdit}.
6644 */
6645 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006646 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006647 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006648
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006649 /**
6650 * Called by the framework in response to a private command from the
6651 * current method, provided by it calling
6652 * {@link InputConnection#performPrivateCommand
6653 * InputConnection.performPrivateCommand()}.
6654 *
6655 * @param action The action name of the command.
6656 * @param data Any additional data for the command. This may be null.
6657 * @return Return true if you handled the command, else false.
6658 */
6659 public boolean onPrivateIMECommand(String action, Bundle data) {
6660 return false;
6661 }
6662
6663 private void nullLayouts() {
6664 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6665 mSavedLayout = (BoringLayout) mLayout;
6666 }
6667 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6668 mSavedHintLayout = (BoringLayout) mHintLayout;
6669 }
6670
Adam Powell282e3772011-08-30 16:51:11 -07006671 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07006672
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08006673 mBoring = mHintBoring = null;
6674
Gilles Debunne77f18b02010-10-22 14:28:25 -07006675 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006676 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006677 }
6678
6679 /**
6680 * Make a new Layout based on the already-measured size of the view,
6681 * on the assumption that it was measured correctly at some point.
6682 */
6683 private void assumeLayout() {
6684 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6685
6686 if (width < 1) {
6687 width = 0;
6688 }
6689
6690 int physicalWidth = width;
6691
6692 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08006693 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006694 }
6695
6696 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6697 physicalWidth, false);
6698 }
6699
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006700 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006701 Layout.Alignment alignment;
6702 switch (getTextAlignment()) {
6703 case TEXT_ALIGNMENT_GRAVITY:
6704 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6705 case Gravity.START:
6706 alignment = Layout.Alignment.ALIGN_NORMAL;
6707 break;
6708 case Gravity.END:
6709 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6710 break;
6711 case Gravity.LEFT:
6712 alignment = Layout.Alignment.ALIGN_LEFT;
6713 break;
6714 case Gravity.RIGHT:
6715 alignment = Layout.Alignment.ALIGN_RIGHT;
6716 break;
6717 case Gravity.CENTER_HORIZONTAL:
6718 alignment = Layout.Alignment.ALIGN_CENTER;
6719 break;
6720 default:
6721 alignment = Layout.Alignment.ALIGN_NORMAL;
6722 break;
6723 }
6724 break;
6725 case TEXT_ALIGNMENT_TEXT_START:
6726 alignment = Layout.Alignment.ALIGN_NORMAL;
6727 break;
6728 case TEXT_ALIGNMENT_TEXT_END:
6729 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6730 break;
6731 case TEXT_ALIGNMENT_CENTER:
6732 alignment = Layout.Alignment.ALIGN_CENTER;
6733 break;
6734 case TEXT_ALIGNMENT_VIEW_START:
6735 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6736 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6737 break;
6738 case TEXT_ALIGNMENT_VIEW_END:
6739 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6740 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6741 break;
6742 case TEXT_ALIGNMENT_INHERIT:
6743 // This should never happen as we have already resolved the text alignment
6744 // but better safe than sorry so we just fall through
6745 default:
6746 alignment = Layout.Alignment.ALIGN_NORMAL;
6747 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006748 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006749 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006750 }
6751
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006752 /**
6753 * The width passed in is now the desired layout width,
6754 * not the full view width with padding.
6755 * {@hide}
6756 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07006757 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006758 BoringLayout.Metrics boring,
6759 BoringLayout.Metrics hintBoring,
6760 int ellipsisWidth, boolean bringIntoView) {
6761 stopMarquee();
6762
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006763 // Update "old" cached values
6764 mOldMaximum = mMaximum;
6765 mOldMaxMode = mMaxMode;
6766
Gilles Debunne83051b82012-02-24 20:01:13 -08006767 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006768
Gilles Debunne287d6c62011-10-05 18:22:11 -07006769 if (wantWidth < 0) {
6770 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006771 }
6772 if (hintWidth < 0) {
6773 hintWidth = 0;
6774 }
6775
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006776 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006777 final boolean testDirChange = mSingleLine && mLayout != null &&
6778 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6779 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6780 int oldDir = 0;
6781 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006782 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006783 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6784 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6785 TruncateAt effectiveEllipsize = mEllipsize;
6786 if (mEllipsize == TruncateAt.MARQUEE &&
6787 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006788 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006789 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006790
Doug Feltcb3791202011-07-07 11:57:48 -07006791 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006792 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006794
Gilles Debunne287d6c62011-10-05 18:22:11 -07006795 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006796 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6797 if (switchEllipsize) {
6798 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6799 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006800 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006801 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006802 }
6803
Romain Guy4dc4f732009-06-19 15:16:40 -07006804 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006805 mHintLayout = null;
6806
6807 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006808 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006809
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006810 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006811 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006812 mHintBoring);
6813 if (hintBoring != null) {
6814 mHintBoring = hintBoring;
6815 }
6816 }
6817
6818 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006819 if (hintBoring.width <= hintWidth &&
6820 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006821 if (mSavedHintLayout != null) {
6822 mHintLayout = mSavedHintLayout.
6823 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006824 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6825 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006826 } else {
6827 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006828 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6829 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006830 }
6831
6832 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006833 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6834 if (mSavedHintLayout != null) {
6835 mHintLayout = mSavedHintLayout.
6836 replaceOrMake(mHint, mTextPaint,
6837 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6838 hintBoring, mIncludePad, mEllipsize,
6839 ellipsisWidth);
6840 } else {
6841 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6842 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6843 hintBoring, mIncludePad, mEllipsize,
6844 ellipsisWidth);
6845 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006846 }
Raph Levien39b4db72015-03-25 13:18:20 -07006847 }
6848 // TODO: code duplication with makeSingleLayout()
6849 if (mHintLayout == null) {
6850 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
Raph Levienebd66ca2015-04-30 15:27:57 -07006851 mHint.length(), mTextPaint, hintWidth)
Raph Levien39b4db72015-03-25 13:18:20 -07006852 .setAlignment(alignment)
Raph Leviena6a08282015-06-03 13:20:45 -07006853 .setTextDirection(mTextDir)
Raph Levien531c30c2015-04-30 16:29:59 -07006854 .setLineSpacing(mSpacingAdd, mSpacingMult)
Raph Levien39b4db72015-03-25 13:18:20 -07006855 .setIncludePad(mIncludePad)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006856 .setBreakStrategy(mBreakStrategy)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006857 .setHyphenationFrequency(mHyphenationFrequency)
6858 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
Raph Levien39b4db72015-03-25 13:18:20 -07006859 if (shouldEllipsize) {
6860 builder.setEllipsize(mEllipsize)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006861 .setEllipsizedWidth(ellipsisWidth);
Raph Levien39b4db72015-03-25 13:18:20 -07006862 }
6863 mHintLayout = builder.build();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006864 }
6865 }
6866
Raph Levienf5cf6c92013-04-12 11:31:31 -07006867 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006868 registerForPreDraw();
6869 }
6870
6871 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006872 if (!compressText(ellipsisWidth)) {
Chet Haase4610eef2015-12-03 07:38:11 -08006873 final int height = mLayoutParams.height;
Romain Guy939151f2009-04-08 14:22:40 -07006874 // If the size of the view does not depend on the size of the text, try to
6875 // start the marquee immediately
Chet Haase4610eef2015-12-03 07:38:11 -08006876 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006877 startMarquee();
6878 } else {
6879 // Defer the start of the marquee until we know our width (see setFrame())
6880 mRestartMarquee = true;
6881 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006882 }
6883 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006884
6885 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006886 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006887 }
6888
Selim Cinek4fb12d32015-11-19 18:10:48 -08006889 /**
6890 * @hide
6891 */
6892 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006893 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6894 boolean useSaved) {
6895 Layout result = null;
6896 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006897 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006898 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6899 mBreakStrategy, mHyphenationFrequency,
Raph Levien39b4db72015-03-25 13:18:20 -07006900 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006901 } else {
6902 if (boring == UNKNOWN_BORING) {
6903 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6904 if (boring != null) {
6905 mBoring = boring;
6906 }
6907 }
6908
6909 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006910 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006911 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6912 if (useSaved && mSavedLayout != null) {
6913 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006914 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006915 boring, mIncludePad);
6916 } else {
6917 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006918 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006919 boring, mIncludePad);
6920 }
6921
6922 if (useSaved) {
6923 mSavedLayout = (BoringLayout) result;
6924 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006925 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006926 if (useSaved && mSavedLayout != null) {
6927 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006928 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006929 boring, mIncludePad, effectiveEllipsize,
6930 ellipsisWidth);
6931 } else {
6932 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006933 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006934 boring, mIncludePad, effectiveEllipsize,
6935 ellipsisWidth);
6936 }
Adam Powell282e3772011-08-30 16:51:11 -07006937 }
Adam Powell282e3772011-08-30 16:51:11 -07006938 }
6939 }
Raph Levien39b4db72015-03-25 13:18:20 -07006940 if (result == null) {
6941 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
Raph Levienebd66ca2015-04-30 15:27:57 -07006942 0, mTransformed.length(), mTextPaint, wantWidth)
Raph Levien39b4db72015-03-25 13:18:20 -07006943 .setAlignment(alignment)
Raph Leviena6a08282015-06-03 13:20:45 -07006944 .setTextDirection(mTextDir)
Raph Levien531c30c2015-04-30 16:29:59 -07006945 .setLineSpacing(mSpacingAdd, mSpacingMult)
Raph Levien39b4db72015-03-25 13:18:20 -07006946 .setIncludePad(mIncludePad)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006947 .setBreakStrategy(mBreakStrategy)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006948 .setHyphenationFrequency(mHyphenationFrequency)
6949 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
Raph Levien39b4db72015-03-25 13:18:20 -07006950 if (shouldEllipsize) {
6951 builder.setEllipsize(effectiveEllipsize)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006952 .setEllipsizedWidth(ellipsisWidth);
6953
Raph Levien39b4db72015-03-25 13:18:20 -07006954 }
6955 // TODO: explore always setting maxLines
6956 result = builder.build();
6957 }
Adam Powell282e3772011-08-30 16:51:11 -07006958 return result;
6959 }
6960
Romain Guy939151f2009-04-08 14:22:40 -07006961 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006962 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006963
Romain Guy3373ed62009-05-04 14:13:32 -07006964 // Only compress the text if it hasn't been compressed by the previous pass
6965 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6966 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006967 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006968 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006969 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6970 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6971 post(new Runnable() {
6972 public void run() {
6973 requestLayout();
6974 }
6975 });
6976 return true;
6977 }
6978 }
6979
6980 return false;
6981 }
6982
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006983 private static int desired(Layout layout) {
6984 int n = layout.getLineCount();
6985 CharSequence text = layout.getText();
6986 float max = 0;
6987
6988 // if any line was wrapped, we can't use it.
6989 // but it's ok for the last line not to have a newline
6990
6991 for (int i = 0; i < n - 1; i++) {
6992 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6993 return -1;
6994 }
6995
6996 for (int i = 0; i < n; i++) {
6997 max = Math.max(max, layout.getLineWidth(i));
6998 }
6999
Neil Fuller33253a42014-10-01 11:55:10 +01007000 return (int) Math.ceil(max);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007001 }
7002
7003 /**
7004 * Set whether the TextView includes extra top and bottom padding to make
7005 * room for accents that go above the normal ascent and descent.
7006 * The default is true.
7007 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007008 * @see #getIncludeFontPadding()
7009 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007010 * @attr ref android.R.styleable#TextView_includeFontPadding
7011 */
7012 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07007013 if (mIncludePad != includepad) {
7014 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007015
Gilles Debunne22378292011-08-12 10:38:52 -07007016 if (mLayout != null) {
7017 nullLayouts();
7018 requestLayout();
7019 invalidate();
7020 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007021 }
7022 }
7023
Gilles Debunnef03acef2012-04-30 19:26:19 -07007024 /**
7025 * Gets whether the TextView includes extra top and bottom padding to make
7026 * room for accents that go above the normal ascent and descent.
7027 *
7028 * @see #setIncludeFontPadding(boolean)
7029 *
7030 * @attr ref android.R.styleable#TextView_includeFontPadding
7031 */
7032 public boolean getIncludeFontPadding() {
7033 return mIncludePad;
7034 }
7035
Romain Guy4dc4f732009-06-19 15:16:40 -07007036 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007037
7038 @Override
7039 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7040 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7041 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7042 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7043 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7044
7045 int width;
7046 int height;
7047
7048 BoringLayout.Metrics boring = UNKNOWN_BORING;
7049 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7050
Doug Feltcb3791202011-07-07 11:57:48 -07007051 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07007052 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07007053 }
7054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007055 int des = -1;
7056 boolean fromexisting = false;
7057
7058 if (widthMode == MeasureSpec.EXACTLY) {
7059 // Parent has told us how big to be. So be it.
7060 width = widthSize;
7061 } else {
7062 if (mLayout != null && mEllipsize == null) {
7063 des = desired(mLayout);
7064 }
7065
7066 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07007067 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007068 if (boring != null) {
7069 mBoring = boring;
7070 }
7071 } else {
7072 fromexisting = true;
7073 }
7074
7075 if (boring == null || boring == UNKNOWN_BORING) {
7076 if (des < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01007077 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007078 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007079 width = des;
7080 } else {
7081 width = boring.width;
7082 }
7083
7084 final Drawables dr = mDrawables;
7085 if (dr != null) {
7086 width = Math.max(width, dr.mDrawableWidthTop);
7087 width = Math.max(width, dr.mDrawableWidthBottom);
7088 }
7089
7090 if (mHint != null) {
7091 int hintDes = -1;
7092 int hintWidth;
7093
Romain Guy4dc4f732009-06-19 15:16:40 -07007094 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007095 hintDes = desired(mHintLayout);
7096 }
7097
7098 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007099 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007100 if (hintBoring != null) {
7101 mHintBoring = hintBoring;
7102 }
7103 }
7104
7105 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7106 if (hintDes < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01007107 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007108 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007109 hintWidth = hintDes;
7110 } else {
7111 hintWidth = hintBoring.width;
7112 }
7113
7114 if (hintWidth > width) {
7115 width = hintWidth;
7116 }
7117 }
7118
7119 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7120
7121 if (mMaxWidthMode == EMS) {
7122 width = Math.min(width, mMaxWidth * getLineHeight());
7123 } else {
7124 width = Math.min(width, mMaxWidth);
7125 }
7126
7127 if (mMinWidthMode == EMS) {
7128 width = Math.max(width, mMinWidth * getLineHeight());
7129 } else {
7130 width = Math.max(width, mMinWidth);
7131 }
7132
7133 // Check against our minimum width
7134 width = Math.max(width, getSuggestedMinimumWidth());
7135
7136 if (widthMode == MeasureSpec.AT_MOST) {
7137 width = Math.min(widthSize, width);
7138 }
7139 }
7140
7141 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7142 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08007143
Jeff Brown033a0012011-11-11 15:30:16 -08007144 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08007145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007146 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007147 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007148
7149 if (mLayout == null) {
7150 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07007151 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007152 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007153 final boolean layoutChanged = (mLayout.getWidth() != want) ||
7154 (hintWidth != hintWant) ||
7155 (mLayout.getEllipsizedWidth() !=
7156 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7157
7158 final boolean widthChanged = (mHint == null) &&
7159 (mEllipsize == null) &&
7160 (want > mLayout.getWidth()) &&
7161 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7162
7163 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7164
7165 if (layoutChanged || maximumChanged) {
7166 if (!maximumChanged && widthChanged) {
7167 mLayout.increaseWidthTo(want);
7168 } else {
7169 makeNewLayout(want, hintWant, boring, hintBoring,
7170 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7171 }
7172 } else {
7173 // Nothing has changed
7174 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007175 }
7176
7177 if (heightMode == MeasureSpec.EXACTLY) {
7178 // Parent has told us how big to be. So be it.
7179 height = heightSize;
7180 mDesiredHeightAtMeasure = -1;
7181 } else {
7182 int desired = getDesiredHeight();
7183
7184 height = desired;
7185 mDesiredHeightAtMeasure = desired;
7186
7187 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02007188 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007189 }
7190 }
7191
Romain Guy4dc4f732009-06-19 15:16:40 -07007192 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007193 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07007194 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007195 }
7196
7197 /*
7198 * We didn't let makeNewLayout() register to bring the cursor into view,
7199 * so do it here if there is any possibility that it is needed.
7200 */
7201 if (mMovement != null ||
7202 mLayout.getWidth() > unpaddedWidth ||
7203 mLayout.getHeight() > unpaddedHeight) {
7204 registerForPreDraw();
7205 } else {
7206 scrollTo(0, 0);
7207 }
7208
7209 setMeasuredDimension(width, height);
7210 }
7211
7212 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007213 return Math.max(
7214 getDesiredHeight(mLayout, true),
7215 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007216 }
7217
7218 private int getDesiredHeight(Layout layout, boolean cap) {
7219 if (layout == null) {
7220 return 0;
7221 }
7222
7223 int linecount = layout.getLineCount();
7224 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7225 int desired = layout.getLineTop(linecount);
7226
7227 final Drawables dr = mDrawables;
7228 if (dr != null) {
7229 desired = Math.max(desired, dr.mDrawableHeightLeft);
7230 desired = Math.max(desired, dr.mDrawableHeightRight);
7231 }
7232
7233 desired += pad;
7234
7235 if (mMaxMode == LINES) {
7236 /*
7237 * Don't cap the hint to a certain number of lines.
7238 * (Do cap it, though, if we have a maximum pixel height.)
7239 */
7240 if (cap) {
7241 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08007242 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007243
7244 if (dr != null) {
7245 desired = Math.max(desired, dr.mDrawableHeightLeft);
7246 desired = Math.max(desired, dr.mDrawableHeightRight);
7247 }
7248
7249 desired += pad;
7250 linecount = mMaximum;
7251 }
7252 }
7253 } else {
7254 desired = Math.min(desired, mMaximum);
7255 }
7256
7257 if (mMinMode == LINES) {
7258 if (linecount < mMinimum) {
7259 desired += getLineHeight() * (mMinimum - linecount);
7260 }
7261 } else {
7262 desired = Math.max(desired, mMinimum);
7263 }
7264
7265 // Check against our minimum height
7266 desired = Math.max(desired, getSuggestedMinimumHeight());
7267
7268 return desired;
7269 }
7270
7271 /**
7272 * Check whether a change to the existing text layout requires a
7273 * new view layout.
7274 */
7275 private void checkForResize() {
Chet Haase4610eef2015-12-03 07:38:11 -08007276 boolean sizeChanged = false;
7277
7278 if (mLayout != null) {
7279 // Check if our width changed
7280 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7281 sizeChanged = true;
7282 invalidate();
7283 }
7284
7285 // Check if our height changed
7286 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7287 int desiredHeight = getDesiredHeight();
7288
7289 if (desiredHeight != this.getHeight()) {
7290 sizeChanged = true;
7291 }
7292 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7293 if (mDesiredHeightAtMeasure >= 0) {
7294 int desiredHeight = getDesiredHeight();
7295
7296 if (desiredHeight != mDesiredHeightAtMeasure) {
7297 sizeChanged = true;
7298 }
7299 }
7300 }
7301 }
7302
7303 if (sizeChanged) {
7304 requestLayout();
7305 // caller will have already invalidated
7306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007307 }
7308
7309 /**
7310 * Check whether entirely new text requires a new view layout
7311 * or merely a new text layout.
7312 */
7313 private void checkForRelayout() {
Chet Haase4610eef2015-12-03 07:38:11 -08007314 // If we have a fixed width, we can just swap in a new text layout
7315 // if the text height stays the same or if the view height is fixed.
7316
7317 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7318 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7319 (mHint == null || mHintLayout != null) &&
7320 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7321 // Static width, so try making a new text layout.
7322
7323 int oldht = mLayout.getHeight();
7324 int want = mLayout.getWidth();
7325 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7326
7327 /*
7328 * No need to bring the text into view, since the size is not
7329 * changing (unless we do the requestLayout(), in which case it
7330 * will happen at measure).
7331 */
7332 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7333 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7334 false);
7335
7336 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7337 // In a fixed-height view, so use our new text layout.
7338 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7339 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7340 invalidate();
7341 return;
7342 }
7343
7344 // Dynamic height, but height has stayed the same,
7345 // so use our new text layout.
7346 if (mLayout.getHeight() == oldht &&
7347 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7348 invalidate();
7349 return;
7350 }
7351 }
7352
7353 // We lose: the height has changed and we have a dynamic height.
7354 // Request a new view layout using our new text layout.
7355 requestLayout();
7356 invalidate();
7357 } else {
7358 // Dynamic width, so we have no choice but to request a new
7359 // view layout with a new text layout.
7360 nullLayouts();
7361 requestLayout();
7362 invalidate();
7363 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007364 }
7365
Gilles Debunne954325e2012-01-25 11:57:06 -08007366 @Override
7367 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7368 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07007369 if (mDeferScroll >= 0) {
7370 int curs = mDeferScroll;
7371 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07007372 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07007373 }
Gilles Debunne954325e2012-01-25 11:57:06 -08007374 }
7375
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007376 private boolean isShowingHint() {
7377 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7378 }
7379
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007380 /**
7381 * Returns true if anything changed.
7382 */
7383 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007384 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007385 int line = 0;
7386 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007387 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007388 }
7389
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007390 Layout.Alignment a = layout.getParagraphAlignment(line);
7391 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007392 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7393 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007394 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007395
7396 int scrollx, scrolly;
7397
Doug Felt25b9f422011-07-11 13:48:37 -07007398 // Convert to left, center, or right alignment.
7399 if (a == Layout.Alignment.ALIGN_NORMAL) {
7400 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7401 Layout.Alignment.ALIGN_RIGHT;
7402 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7403 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7404 Layout.Alignment.ALIGN_LEFT;
7405 }
7406
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007407 if (a == Layout.Alignment.ALIGN_CENTER) {
7408 /*
7409 * Keep centered if possible, or, if it is too wide to fit,
7410 * keep leading edge in view.
7411 */
7412
Neil Fuller33253a42014-10-01 11:55:10 +01007413 int left = (int) Math.floor(layout.getLineLeft(line));
7414 int right = (int) Math.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007415
7416 if (right - left < hspace) {
7417 scrollx = (right + left) / 2 - hspace / 2;
7418 } else {
7419 if (dir < 0) {
7420 scrollx = right - hspace;
7421 } else {
7422 scrollx = left;
7423 }
7424 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007425 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Neil Fuller33253a42014-10-01 11:55:10 +01007426 int right = (int) Math.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07007427 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007428 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Neil Fuller33253a42014-10-01 11:55:10 +01007429 scrollx = (int) Math.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007430 }
7431
7432 if (ht < vspace) {
7433 scrolly = 0;
7434 } else {
7435 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7436 scrolly = ht - vspace;
7437 } else {
7438 scrolly = 0;
7439 }
7440 }
7441
7442 if (scrollx != mScrollX || scrolly != mScrollY) {
7443 scrollTo(scrollx, scrolly);
7444 return true;
7445 } else {
7446 return false;
7447 }
7448 }
7449
7450 /**
7451 * Move the point, specified by the offset, into the view if it is needed.
7452 * This has to be called after layout. Returns true if anything changed.
7453 */
7454 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07007455 if (isLayoutRequested()) {
7456 mDeferScroll = offset;
7457 return false;
7458 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007459 boolean changed = false;
7460
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007461 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07007462
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007463 if (layout == null) return changed;
7464
7465 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007466
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007467 int grav;
7468
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007469 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07007470 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007471 grav = 1;
7472 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007473 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007474 grav = -1;
7475 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007476 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007477 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007478 break;
7479 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007480 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007481 break;
7482 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007483 default:
7484 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07007485 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007486 }
7487
Raph Levienafe8e9b2012-12-19 16:09:32 -08007488 // We only want to clamp the cursor to fit within the layout width
7489 // in left-to-right modes, because in a right to left alignment,
7490 // we want to scroll to keep the line-right on the screen, as other
7491 // lines are likely to have text flush with the right margin, which
7492 // we want to keep visible.
7493 // A better long-term solution would probably be to measure both
7494 // the full line and a blank-trimmed version, and, for example, use
7495 // the latter measurement for centering and right alignment, but for
7496 // the time being we only implement the cursor clamping in left to
7497 // right where it is most likely to be annoying.
7498 final boolean clamped = grav > 0;
7499 // FIXME: Is it okay to truncate this, or should we round?
7500 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7501 final int top = layout.getLineTop(line);
7502 final int bottom = layout.getLineTop(line + 1);
7503
Neil Fuller33253a42014-10-01 11:55:10 +01007504 int left = (int) Math.floor(layout.getLineLeft(line));
7505 int right = (int) Math.ceil(layout.getLineRight(line));
Raph Levienafe8e9b2012-12-19 16:09:32 -08007506 int ht = layout.getHeight();
7507
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007508 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7509 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08007510 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7511 // If cursor has been clamped, make sure we don't scroll.
7512 right = Math.max(x, left + hspace);
7513 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007514
7515 int hslack = (bottom - top) / 2;
7516 int vslack = hslack;
7517
7518 if (vslack > vspace / 4)
7519 vslack = vspace / 4;
7520 if (hslack > hspace / 4)
7521 hslack = hspace / 4;
7522
7523 int hs = mScrollX;
7524 int vs = mScrollY;
7525
7526 if (top - vs < vslack)
7527 vs = top - vslack;
7528 if (bottom - vs > vspace - vslack)
7529 vs = bottom - (vspace - vslack);
7530 if (ht - vs < vspace)
7531 vs = ht - vspace;
7532 if (0 - vs > 0)
7533 vs = 0;
7534
7535 if (grav != 0) {
7536 if (x - hs < hslack) {
7537 hs = x - hslack;
7538 }
7539 if (x - hs > hspace - hslack) {
7540 hs = x - (hspace - hslack);
7541 }
7542 }
7543
7544 if (grav < 0) {
7545 if (left - hs > 0)
7546 hs = left;
7547 if (right - hs < hspace)
7548 hs = right - hspace;
7549 } else if (grav > 0) {
7550 if (right - hs < hspace)
7551 hs = right - hspace;
7552 if (left - hs > 0)
7553 hs = left;
7554 } else /* grav == 0 */ {
7555 if (right - left <= hspace) {
7556 /*
7557 * If the entire text fits, center it exactly.
7558 */
7559 hs = left - (hspace - (right - left)) / 2;
7560 } else if (x > right - hslack) {
7561 /*
7562 * If we are near the right edge, keep the right edge
7563 * at the edge of the view.
7564 */
7565 hs = right - hspace;
7566 } else if (x < left + hslack) {
7567 /*
7568 * If we are near the left edge, keep the left edge
7569 * at the edge of the view.
7570 */
7571 hs = left;
7572 } else if (left > hs) {
7573 /*
7574 * Is there whitespace visible at the left? Fix it if so.
7575 */
7576 hs = left;
7577 } else if (right < hs + hspace) {
7578 /*
7579 * Is there whitespace visible at the right? Fix it if so.
7580 */
7581 hs = right - hspace;
7582 } else {
7583 /*
7584 * Otherwise, float as needed.
7585 */
7586 if (x - hs < hslack) {
7587 hs = x - hslack;
7588 }
7589 if (x - hs > hspace - hslack) {
7590 hs = x - (hspace - hslack);
7591 }
7592 }
7593 }
7594
7595 if (hs != mScrollX || vs != mScrollY) {
7596 if (mScroller == null) {
7597 scrollTo(hs, vs);
7598 } else {
7599 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7600 int dx = hs - mScrollX;
7601 int dy = vs - mScrollY;
7602
7603 if (duration > ANIMATED_SCROLL_GAP) {
7604 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07007605 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007606 invalidate();
7607 } else {
7608 if (!mScroller.isFinished()) {
7609 mScroller.abortAnimation();
7610 }
7611
7612 scrollBy(dx, dy);
7613 }
7614
7615 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7616 }
7617
7618 changed = true;
7619 }
7620
7621 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08007622 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7623 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007624
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007625 // The offsets here are to ensure the rectangle we are using is
7626 // within our view bounds, in case the cursor is on the far left
7627 // or right. If it isn't withing the bounds, then this request
7628 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08007629 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007630 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08007631 getInterestingRect(mTempRect, line);
7632 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007633
Gilles Debunne716dbf62011-03-07 18:12:10 -08007634 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007635 changed = true;
7636 }
7637 }
7638
7639 return changed;
7640 }
7641
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007642 /**
7643 * Move the cursor, if needed, so that it is at an offset that is visible
7644 * to the user. This will not move the cursor if it represents more than
7645 * one character (a selection range). This will only work if the
7646 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007647 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07007648 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007649 */
7650 public boolean moveCursorToVisibleOffset() {
7651 if (!(mText instanceof Spannable)) {
7652 return false;
7653 }
Gilles Debunne05336272010-07-09 20:13:45 -07007654 int start = getSelectionStart();
7655 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007656 if (start != end) {
7657 return false;
7658 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007659
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007660 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007661
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007662 int line = mLayout.getLineForOffset(start);
7663
7664 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007665 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007666 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7667 int vslack = (bottom - top) / 2;
7668 if (vslack > vspace / 4)
7669 vslack = vspace / 4;
7670 final int vs = mScrollY;
7671
7672 if (top < (vs+vslack)) {
7673 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7674 } else if (bottom > (vspace+vs-vslack)) {
7675 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7676 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007677
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007678 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007679
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007680 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7681 final int hs = mScrollX;
7682 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7683 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07007684
Doug Feltc982f602010-05-25 11:51:40 -07007685 // line might contain bidirectional text
7686 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7687 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7688
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007689 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07007690 if (newStart < lowChar) {
7691 newStart = lowChar;
7692 } else if (newStart > highChar) {
7693 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007694 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007695
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007696 if (newStart != start) {
7697 Selection.setSelection((Spannable)mText, newStart);
7698 return true;
7699 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007700
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007701 return false;
7702 }
7703
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007704 @Override
7705 public void computeScroll() {
7706 if (mScroller != null) {
7707 if (mScroller.computeScrollOffset()) {
7708 mScrollX = mScroller.getCurrX();
7709 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08007710 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007711 postInvalidate(); // So we draw again
7712 }
7713 }
7714 }
7715
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007716 private void getInterestingRect(Rect r, int line) {
7717 convertFromViewportToContentCoordinates(r);
7718
7719 // Rectangle can can be expanded on first and last line to take
7720 // padding into account.
7721 // TODO Take left/right padding into account too?
7722 if (line == 0) r.top -= getExtendedPaddingTop();
7723 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7724 }
7725
7726 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007727 final int horizontalOffset = viewportToContentHorizontalOffset();
7728 r.left += horizontalOffset;
7729 r.right += horizontalOffset;
7730
7731 final int verticalOffset = viewportToContentVerticalOffset();
7732 r.top += verticalOffset;
7733 r.bottom += verticalOffset;
7734 }
7735
Gilles Debunned88876a2012-03-16 17:34:04 -07007736 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007737 return getCompoundPaddingLeft() - mScrollX;
7738 }
7739
Gilles Debunned88876a2012-03-16 17:34:04 -07007740 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007741 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007742 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007743 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007744 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007745 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007746 }
7747
7748 @Override
7749 public void debug(int depth) {
7750 super.debug(depth);
7751
7752 String output = debugIndent(depth);
7753 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7754 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7755 + "} ";
7756
7757 if (mText != null) {
7758
7759 output += "mText=\"" + mText + "\" ";
7760 if (mLayout != null) {
7761 output += "mLayout width=" + mLayout.getWidth()
7762 + " height=" + mLayout.getHeight();
7763 }
7764 } else {
7765 output += "mText=NULL";
7766 }
7767 Log.d(VIEW_LOG_TAG, output);
7768 }
7769
7770 /**
7771 * Convenience for {@link Selection#getSelectionStart}.
7772 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007773 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007774 public int getSelectionStart() {
7775 return Selection.getSelectionStart(getText());
7776 }
7777
7778 /**
7779 * Convenience for {@link Selection#getSelectionEnd}.
7780 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007781 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007782 public int getSelectionEnd() {
7783 return Selection.getSelectionEnd(getText());
7784 }
7785
7786 /**
7787 * Return true iff there is a selection inside this text view.
7788 */
7789 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007790 final int selectionStart = getSelectionStart();
7791 final int selectionEnd = getSelectionEnd();
7792
7793 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007794 }
7795
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007796 String getSelectedText() {
Seigo Nonaka86d60cd2015-06-25 19:02:17 +09007797 if (!hasSelection()) {
7798 return null;
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007799 }
Seigo Nonaka86d60cd2015-06-25 19:02:17 +09007800
7801 final int start = getSelectionStart();
7802 final int end = getSelectionEnd();
7803 return String.valueOf(
7804 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007805 }
7806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007807 /**
7808 * Sets the properties of this field (lines, horizontally scrolling,
7809 * transformation method) to be for a single-line input.
7810 *
7811 * @attr ref android.R.styleable#TextView_singleLine
7812 */
7813 public void setSingleLine() {
7814 setSingleLine(true);
7815 }
7816
7817 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007818 * Sets the properties of this field to transform input to ALL CAPS
7819 * display. This may use a "small caps" formatting if available.
7820 * This setting will be ignored if this field is editable or selectable.
7821 *
7822 * This call replaces the current transformation method. Disabling this
7823 * will not necessarily restore the previous behavior from before this
7824 * was enabled.
7825 *
7826 * @see #setTransformationMethod(TransformationMethod)
7827 * @attr ref android.R.styleable#TextView_textAllCaps
7828 */
7829 public void setAllCaps(boolean allCaps) {
7830 if (allCaps) {
7831 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7832 } else {
7833 setTransformationMethod(null);
7834 }
7835 }
7836
7837 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007838 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7839 * transformation method) to be for a single-line input; if false, restores these to the default
7840 * conditions.
7841 *
7842 * Note that the default conditions are not necessarily those that were in effect prior this
7843 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007844 *
7845 * @attr ref android.R.styleable#TextView_singleLine
7846 */
7847 @android.view.RemotableViewMethod
7848 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007849 // Could be used, but may break backward compatibility.
7850 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007851 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007852 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007853 }
7854
7855 /**
7856 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7857 * @param singleLine
7858 */
7859 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007860 if (mEditor != null &&
7861 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007862 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007863 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007864 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007865 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007866 }
7867 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007868 }
7869
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007870 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7871 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007872 mSingleLine = singleLine;
7873 if (singleLine) {
7874 setLines(1);
7875 setHorizontallyScrolling(true);
7876 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007877 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007878 }
7879 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007880 if (changeMaxLines) {
7881 setMaxLines(Integer.MAX_VALUE);
7882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007883 setHorizontallyScrolling(false);
7884 if (applyTransformation) {
7885 setTransformationMethod(null);
7886 }
7887 }
7888 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007889
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007890 /**
7891 * Causes words in the text that are longer than the view is wide
7892 * to be ellipsized instead of broken in the middle. You may also
7893 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007894 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007895 * to turn off ellipsizing.
7896 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007897 * If {@link #setMaxLines} has been used to set two or more lines,
Newton Allen8f8a11b2013-11-26 10:25:38 -08007898 * only {@link android.text.TextUtils.TruncateAt#END} and
7899 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
Gilles Debunne6435a562011-08-04 21:22:30 -07007900 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007901 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007902 * @attr ref android.R.styleable#TextView_ellipsize
7903 */
7904 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007905 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7906 if (mEllipsize != where) {
7907 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007908
Gilles Debunne22378292011-08-12 10:38:52 -07007909 if (mLayout != null) {
7910 nullLayouts();
7911 requestLayout();
7912 invalidate();
7913 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007914 }
7915 }
7916
7917 /**
7918 * Sets how many times to repeat the marquee animation. Only applied if the
7919 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7920 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007921 * @see #getMarqueeRepeatLimit()
7922 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007923 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7924 */
7925 public void setMarqueeRepeatLimit(int marqueeLimit) {
7926 mMarqueeRepeatLimit = marqueeLimit;
7927 }
7928
7929 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007930 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7931 * TextView has marquee enabled.
7932 *
7933 * @return the number of times the marquee animation is repeated. -1 if the animation
7934 * repeats indefinitely
7935 *
7936 * @see #setMarqueeRepeatLimit(int)
7937 *
7938 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7939 */
7940 public int getMarqueeRepeatLimit() {
7941 return mMarqueeRepeatLimit;
7942 }
7943
7944 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007945 * Returns where, if anywhere, words that are longer than the view
7946 * is wide should be ellipsized.
7947 */
7948 @ViewDebug.ExportedProperty
7949 public TextUtils.TruncateAt getEllipsize() {
7950 return mEllipsize;
7951 }
7952
7953 /**
7954 * Set the TextView so that when it takes focus, all the text is
7955 * selected.
7956 *
7957 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7958 */
7959 @android.view.RemotableViewMethod
7960 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007961 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007962 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007963
7964 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7965 setText(mText, BufferType.SPANNABLE);
7966 }
7967 }
7968
7969 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007970 * Set whether the cursor is visible. The default is true. Note that this property only
7971 * makes sense for editable TextView.
7972 *
7973 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007974 *
7975 * @attr ref android.R.styleable#TextView_cursorVisible
7976 */
7977 @android.view.RemotableViewMethod
7978 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007979 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007980 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007981 if (mEditor.mCursorVisible != visible) {
7982 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007983 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007984
Gilles Debunne2d373a12012-04-20 15:32:19 -07007985 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007986
Gilles Debunne3d010062011-02-18 14:16:41 -08007987 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007988 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007990 }
7991
Gilles Debunnef03acef2012-04-30 19:26:19 -07007992 /**
7993 * @return whether or not the cursor is visible (assuming this TextView is editable)
7994 *
7995 * @see #setCursorVisible(boolean)
7996 *
7997 * @attr ref android.R.styleable#TextView_cursorVisible
7998 */
7999 public boolean isCursorVisible() {
8000 // true is the default value
8001 return mEditor == null ? true : mEditor.mCursorVisible;
8002 }
8003
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008004 private boolean canMarquee() {
8005 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07008006 return width > 0 && (mLayout.getLineWidth(0) > width ||
8007 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
8008 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008009 }
8010
8011 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07008012 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08008013 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07008014
Romain Guy939151f2009-04-08 14:22:40 -07008015 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8016 return;
8017 }
8018
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008019 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
8020 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07008021
Adam Powell282e3772011-08-30 16:51:11 -07008022 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8023 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8024 final Layout tmp = mLayout;
8025 mLayout = mSavedMarqueeModeLayout;
8026 mSavedMarqueeModeLayout = tmp;
8027 setHorizontalFadingEdgeEnabled(true);
8028 requestLayout();
8029 invalidate();
8030 }
8031
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008032 if (mMarquee == null) mMarquee = new Marquee(this);
8033 mMarquee.start(mMarqueeRepeatLimit);
8034 }
8035 }
8036
8037 private void stopMarquee() {
8038 if (mMarquee != null && !mMarquee.isStopped()) {
8039 mMarquee.stop();
8040 }
Adam Powell282e3772011-08-30 16:51:11 -07008041
8042 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8043 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8044 final Layout tmp = mSavedMarqueeModeLayout;
8045 mSavedMarqueeModeLayout = mLayout;
8046 mLayout = tmp;
8047 setHorizontalFadingEdgeEnabled(false);
8048 requestLayout();
8049 invalidate();
8050 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008051 }
8052
8053 private void startStopMarquee(boolean start) {
8054 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8055 if (start) {
8056 startMarquee();
8057 } else {
8058 stopMarquee();
8059 }
8060 }
8061 }
8062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008063 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08008064 * This method is called when the text is changed, in case any subclasses
8065 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008066 *
Gilles Debunne4469e602011-03-09 14:38:04 -08008067 * Within <code>text</code>, the <code>lengthAfter</code> characters
8068 * beginning at <code>start</code> have just replaced old text that had
8069 * length <code>lengthBefore</code>. It is an error to attempt to make
8070 * changes to <code>text</code> from this callback.
8071 *
8072 * @param text The text the TextView is displaying
8073 * @param start The offset of the start of the range of the text that was
8074 * modified
8075 * @param lengthBefore The length of the former text that has been replaced
8076 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008077 */
Gilles Debunne4469e602011-03-09 14:38:04 -08008078 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07008079 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008080 }
8081
8082 /**
8083 * This method is called when the selection has changed, in case any
8084 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008085 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008086 * @param selStart The new selection start location.
8087 * @param selEnd The new selection end location.
8088 */
8089 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07008090 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008091 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07008092
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008093 /**
8094 * Adds a TextWatcher to the list of those whose methods are called
8095 * whenever this TextView's text changes.
8096 * <p>
8097 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8098 * not called after {@link #setText} calls. Now, doing {@link #setText}
8099 * if there are any text changed listeners forces the buffer type to
8100 * Editable if it would not otherwise be and does call this method.
8101 */
8102 public void addTextChangedListener(TextWatcher watcher) {
8103 if (mListeners == null) {
8104 mListeners = new ArrayList<TextWatcher>();
8105 }
8106
8107 mListeners.add(watcher);
8108 }
8109
8110 /**
8111 * Removes the specified TextWatcher from the list of those whose
8112 * methods are called
8113 * whenever this TextView's text changes.
8114 */
8115 public void removeTextChangedListener(TextWatcher watcher) {
8116 if (mListeners != null) {
8117 int i = mListeners.indexOf(watcher);
8118
8119 if (i >= 0) {
8120 mListeners.remove(i);
8121 }
8122 }
8123 }
8124
Gilles Debunne6435a562011-08-04 21:22:30 -07008125 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008126 if (mListeners != null) {
8127 final ArrayList<TextWatcher> list = mListeners;
8128 final int count = list.size();
8129 for (int i = 0; i < count; i++) {
8130 list.get(i).beforeTextChanged(text, start, before, after);
8131 }
8132 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008133
8134 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008135 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8136 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07008137 }
8138
8139 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008140 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07008141 if (!(mText instanceof Editable)) return;
8142 Editable text = (Editable) mText;
8143
8144 T[] spans = text.getSpans(start, end, type);
8145 final int length = spans.length;
8146 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008147 final int spanStart = text.getSpanStart(spans[i]);
8148 final int spanEnd = text.getSpanEnd(spans[i]);
8149 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07008150 text.removeSpan(spans[i]);
8151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008152 }
8153
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008154 void removeAdjacentSuggestionSpans(final int pos) {
8155 if (!(mText instanceof Editable)) return;
8156 final Editable text = (Editable) mText;
8157
8158 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8159 final int length = spans.length;
8160 for (int i = 0; i < length; i++) {
8161 final int spanStart = text.getSpanStart(spans[i]);
8162 final int spanEnd = text.getSpanEnd(spans[i]);
8163 if (spanEnd == pos || spanStart == pos) {
8164 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8165 text.removeSpan(spans[i]);
8166 }
8167 }
8168 }
8169 }
8170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008171 /**
8172 * Not private so it can be called from an inner class without going
8173 * through a thunk.
8174 */
Gilles Debunne6435a562011-08-04 21:22:30 -07008175 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008176 if (mListeners != null) {
8177 final ArrayList<TextWatcher> list = mListeners;
8178 final int count = list.size();
8179 for (int i = 0; i < count; i++) {
8180 list.get(i).onTextChanged(text, start, before, after);
8181 }
8182 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01008183
Gilles Debunne2d373a12012-04-20 15:32:19 -07008184 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008185 }
8186
8187 /**
8188 * Not private so it can be called from an inner class without going
8189 * through a thunk.
8190 */
8191 void sendAfterTextChanged(Editable text) {
8192 if (mListeners != null) {
8193 final ArrayList<TextWatcher> list = mListeners;
8194 final int count = list.size();
8195 for (int i = 0; i < count; i++) {
8196 list.get(i).afterTextChanged(text);
8197 }
8198 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07008199 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008200 }
8201
Gilles Debunned88876a2012-03-16 17:34:04 -07008202 void updateAfterEdit() {
8203 invalidate();
8204 int curs = getSelectionStart();
8205
8206 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8207 registerForPreDraw();
8208 }
8209
Raph Levienf5c1a872012-10-15 17:22:26 -07008210 checkForResize();
8211
Gilles Debunned88876a2012-03-16 17:34:04 -07008212 if (curs >= 0) {
8213 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008214 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07008215 bringPointIntoView(curs);
8216 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008217 }
8218
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008219 /**
8220 * Not private so it can be called from an inner class without going
8221 * through a thunk.
8222 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008223 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Andrei Stingaceanu373816e2015-05-28 11:26:28 +01008224 sLastCutCopyOrTextChangedTime = 0;
8225
Gilles Debunne2d373a12012-04-20 15:32:19 -07008226 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008227 if (ims == null || ims.mBatchEditNesting == 0) {
8228 updateAfterEdit();
8229 }
8230 if (ims != null) {
8231 ims.mContentChanged = true;
8232 if (ims.mChangedStart < 0) {
8233 ims.mChangedStart = start;
8234 ims.mChangedEnd = start+before;
8235 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01008236 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8237 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008238 }
8239 ims.mChangedDelta += after-before;
8240 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07008241 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008242 sendOnTextChanged(buffer, start, before, after);
8243 onTextChanged(buffer, start, before, after);
8244 }
Gilles Debunne60e21862012-01-30 15:04:14 -08008245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008246 /**
8247 * Not private so it can be called from an inner class without going
8248 * through a thunk.
8249 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008250 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008251 // XXX Make the start and end move together if this ends up
8252 // spending too much time invalidating.
8253
8254 boolean selChanged = false;
8255 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008256
Gilles Debunne2d373a12012-04-20 15:32:19 -07008257 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008258
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008259 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008260 selChanged = true;
8261 newSelEnd = newStart;
8262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008263 if (oldStart >= 0 || newStart >= 0) {
8264 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07008265 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008266 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008267 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008268 }
8269 }
8270
8271 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008272 selChanged = true;
8273 newSelStart = newStart;
8274
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008275 if (oldStart >= 0 || newStart >= 0) {
8276 int end = Selection.getSelectionEnd(buf);
8277 invalidateCursor(end, oldStart, newStart);
8278 }
8279 }
8280
8281 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008282 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008283 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08008284
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008285 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8286 if (newSelStart < 0) {
8287 newSelStart = Selection.getSelectionStart(buf);
8288 }
8289 if (newSelEnd < 0) {
8290 newSelEnd = Selection.getSelectionEnd(buf);
8291 }
8292 onSelectionChanged(newSelStart, newSelEnd);
8293 }
8294 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08008295
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08008296 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8297 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008298 if (ims == null || ims.mBatchEditNesting == 0) {
8299 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08008300 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008301 checkForResize();
8302 } else {
8303 ims.mContentChanged = true;
8304 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07008305 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008306 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8307 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07008308 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008309 }
8310
8311 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008312 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008313 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8314 ims.mSelectionModeChanged = true;
8315 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008316
8317 if (Selection.getSelectionStart(buf) >= 0) {
8318 if (ims == null || ims.mBatchEditNesting == 0) {
8319 invalidateCursor();
8320 } else {
8321 ims.mCursorChanged = true;
8322 }
8323 }
8324 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008325
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008326 if (what instanceof ParcelableSpan) {
8327 // If this is a span that can be sent to a remote process,
8328 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07008329 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008330 if (ims.mBatchEditNesting != 0) {
8331 if (oldStart >= 0) {
8332 if (ims.mChangedStart > oldStart) {
8333 ims.mChangedStart = oldStart;
8334 }
8335 if (ims.mChangedStart > oldEnd) {
8336 ims.mChangedStart = oldEnd;
8337 }
8338 }
8339 if (newStart >= 0) {
8340 if (ims.mChangedStart > newStart) {
8341 ims.mChangedStart = newStart;
8342 }
8343 if (ims.mChangedStart > newEnd) {
8344 ims.mChangedStart = newEnd;
8345 }
8346 }
8347 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008348 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008349 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07008350 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008351 ims.mContentChanged = true;
8352 }
8353 }
8354 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008355
Gilles Debunne2d373a12012-04-20 15:32:19 -07008356 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8357 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07008358 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07008359 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008360 }
8361
Gilles Debunne6435a562011-08-04 21:22:30 -07008362 /**
Romain Guydcc490f2010-02-24 17:59:35 -08008363 * @hide
8364 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008365 @Override
Romain Guya440b002010-02-24 15:57:54 -08008366 public void dispatchFinishTemporaryDetach() {
8367 mDispatchTemporaryDetach = true;
8368 super.dispatchFinishTemporaryDetach();
8369 mDispatchTemporaryDetach = false;
8370 }
8371
8372 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008373 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008374 super.onStartTemporaryDetach();
8375 // Only track when onStartTemporaryDetach() is called directly,
8376 // usually because this instance is an editable field in a list
8377 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08008378
Adam Powell057a5852012-05-11 10:28:38 -07008379 // Tell the editor that we are temporarily detached. It can use this to preserve
8380 // selection state as needed.
8381 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008382 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008384 @Override
8385 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008386 super.onFinishTemporaryDetach();
8387 // Only track when onStartTemporaryDetach() is called directly,
8388 // usually because this instance is an editable field in a list
8389 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07008390 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008391 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008393 @Override
8394 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8395 if (mTemporaryDetach) {
8396 // If we are temporarily in the detach state, then do nothing.
8397 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8398 return;
8399 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008400
Gilles Debunne2d373a12012-04-20 15:32:19 -07008401 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07008402
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008403 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008404 if (mText instanceof Spannable) {
8405 Spannable sp = (Spannable) mText;
8406 MetaKeyKeyListener.resetMetaState(sp);
8407 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008408 }
8409
8410 startStopMarquee(focused);
8411
8412 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008413 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008414 }
8415
8416 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8417 }
8418
8419 @Override
8420 public void onWindowFocusChanged(boolean hasWindowFocus) {
8421 super.onWindowFocusChanged(hasWindowFocus);
8422
Gilles Debunne2d373a12012-04-20 15:32:19 -07008423 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008424
8425 startStopMarquee(hasWindowFocus);
8426 }
8427
Adam Powellba0a2c32010-09-28 17:41:23 -07008428 @Override
8429 protected void onVisibilityChanged(View changedView, int visibility) {
8430 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08008431 if (mEditor != null && visibility != VISIBLE) {
Mady Mellora2861452015-06-25 08:40:27 -07008432 mEditor.hideCursorAndSpanControllers();
Clara Bayarri7938cdb2015-06-02 20:03:45 +01008433 stopTextActionMode();
Adam Powellba0a2c32010-09-28 17:41:23 -07008434 }
8435 }
8436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008437 /**
8438 * Use {@link BaseInputConnection#removeComposingSpans
8439 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8440 * state from this text view.
8441 */
8442 public void clearComposingText() {
8443 if (mText instanceof Spannable) {
8444 BaseInputConnection.removeComposingSpans((Spannable)mText);
8445 }
8446 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008448 @Override
8449 public void setSelected(boolean selected) {
8450 boolean wasSelected = isSelected();
8451
8452 super.setSelected(selected);
8453
8454 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8455 if (selected) {
8456 startMarquee();
8457 } else {
8458 stopMarquee();
8459 }
8460 }
8461 }
8462
8463 @Override
8464 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008465 final int action = event.getActionMasked();
Mady Mellor2ff2cd82015-03-02 10:37:01 -08008466 if (mEditor != null) {
8467 mEditor.onTouchEvent(event);
8468
8469 if (mEditor.mSelectionModifierCursorController != null &&
8470 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8471 return true;
8472 }
8473 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008474
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008475 final boolean superResult = super.onTouchEvent(event);
8476
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008477 /*
Clara Bayarrib71dddd2015-06-04 23:17:30 +01008478 * Don't handle the release after a long press, because it will move the selection away from
8479 * whatever the menu action was trying to affect. If the long press should have triggered an
8480 * insertion action mode, we can now actually show it.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008481 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07008482 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8483 mEditor.mDiscardNextActionUp = false;
Clara Bayarrib71dddd2015-06-04 23:17:30 +01008484
8485 if (mEditor.mIsInsertionActionModeStartPending) {
8486 mEditor.startInsertionActionMode();
8487 mEditor.mIsInsertionActionModeStartPending = false;
8488 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008489 return superResult;
8490 }
8491
Gilles Debunne70a63122011-09-01 13:27:33 -07008492 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008493 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08008494
Gilles Debunne70a63122011-09-01 13:27:33 -07008495 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03008496 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008497 boolean handled = false;
8498
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07008499 if (mMovement != null) {
8500 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8501 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008502
Gilles Debunne60e21862012-01-30 15:04:14 -08008503 final boolean textIsSelectable = isTextSelectable();
8504 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008505 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07008506 // on non editable text that support text selection.
8507 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008508 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8509 getSelectionEnd(), ClickableSpan.class);
8510
Gilles Debunne822b8f02012-01-17 18:02:15 -08008511 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008512 links[0].onClick(this);
8513 handled = true;
8514 }
8515 }
8516
Gilles Debunne60e21862012-01-30 15:04:14 -08008517 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008518 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09008519 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09008520 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07008521 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008522 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07008523 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008524
Gilles Debunned88876a2012-03-16 17:34:04 -07008525 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07008526 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07008527
8528 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008529 }
8530
The Android Open Source Project4df24232009-03-05 14:34:35 -08008531 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008532 return true;
8533 }
8534 }
8535
8536 return superResult;
8537 }
8538
Jeff Brown8f345672011-02-26 13:29:53 -08008539 @Override
8540 public boolean onGenericMotionEvent(MotionEvent event) {
8541 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8542 try {
8543 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8544 return true;
8545 }
8546 } catch (AbstractMethodError ex) {
8547 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8548 // Ignore its absence in case third party applications implemented the
8549 // interface directly.
8550 }
8551 }
8552 return super.onGenericMotionEvent(event);
8553 }
8554
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09008555 @Override
8556 protected void onCreateContextMenu(ContextMenu menu) {
8557 if (mEditor != null) {
8558 mEditor.onCreateContextMenu(menu);
8559 }
8560 }
8561
8562 @Override
8563 public boolean showContextMenu() {
8564 if (mEditor != null) {
8565 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
8566 }
8567 return super.showContextMenu();
8568 }
8569
8570 @Override
8571 public boolean showContextMenu(float x, float y) {
8572 if (mEditor != null) {
8573 mEditor.setContextMenuAnchor(x, y);
8574 }
8575 return super.showContextMenu(x, y);
8576 }
8577
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008578 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08008579 * @return True iff this TextView contains a text that can be edited, or if this is
8580 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008581 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008582 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08008583 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008584 }
8585
The Android Open Source Project4df24232009-03-05 14:34:35 -08008586 /**
8587 * Returns true, only while processing a touch gesture, if the initial
8588 * 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 -07008589 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07008590 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08008591 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008592 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008593 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08008594 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008595
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008596 @Override
8597 public void cancelLongPress() {
8598 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008599 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008600 }
Gilles Debunne70a63122011-09-01 13:27:33 -07008601
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008602 @Override
8603 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008604 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008605 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8606 return true;
8607 }
8608 }
8609
8610 return super.onTrackballEvent(event);
8611 }
8612
8613 public void setScroller(Scroller s) {
8614 mScroller = s;
8615 }
8616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008617 @Override
8618 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008619 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8620 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008621 if (mMarquee != null && !mMarquee.isStopped()) {
8622 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07008623 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008624 final float scroll = marquee.getScroll();
8625 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07008626 } else {
8627 return 0.0f;
8628 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008629 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008630 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008631 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008632 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008633 case Gravity.LEFT:
8634 return 0.0f;
8635 case Gravity.RIGHT:
8636 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8637 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8638 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8639 case Gravity.CENTER_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008640 case Gravity.FILL_HORIZONTAL:
8641 final int textDirection = mLayout.getParagraphDirection(0);
8642 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8643 return 0.0f;
8644 } else {
8645 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8646 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8647 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8648 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008649 }
8650 }
8651 }
8652 return super.getLeftFadingEdgeStrength();
8653 }
8654
8655 @Override
8656 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008657 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8658 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008659 if (mMarquee != null && !mMarquee.isStopped()) {
8660 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008661 final float maxFadeScroll = marquee.getMaxFadeScroll();
8662 final float scroll = marquee.getScroll();
8663 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008664 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008665 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008666 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008667 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008668 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07008669 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8670 getCompoundPaddingRight();
8671 final float lineWidth = mLayout.getLineWidth(0);
8672 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008673 case Gravity.RIGHT:
8674 return 0.0f;
8675 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07008676 case Gravity.FILL_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008677 final int textDirection = mLayout.getParagraphDirection(0);
8678 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8679 return 0.0f;
8680 } else {
8681 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008682 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8683 getHorizontalFadingEdgeLength();
Raph Levien8079ae12013-09-26 15:57:07 -07008684 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008685 }
8686 }
8687 }
8688 return super.getRightFadingEdgeStrength();
8689 }
8690
8691 @Override
8692 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07008693 if (mLayout != null) {
8694 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8695 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8696 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008697
8698 return super.computeHorizontalScrollRange();
8699 }
8700
8701 @Override
8702 protected int computeVerticalScrollRange() {
8703 if (mLayout != null)
8704 return mLayout.getHeight();
8705
8706 return super.computeVerticalScrollRange();
8707 }
8708
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008709 @Override
8710 protected int computeVerticalScrollExtent() {
8711 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8712 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008713
8714 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07008715 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8716 super.findViewsWithText(outViews, searched, flags);
8717 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8718 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8719 String searchedLowerCase = searched.toString().toLowerCase();
8720 String textLowerCase = mText.toString().toLowerCase();
8721 if (textLowerCase.contains(searchedLowerCase)) {
8722 outViews.add(this);
8723 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008724 }
8725 }
8726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008727 public enum BufferType {
8728 NORMAL, SPANNABLE, EDITABLE,
8729 }
8730
8731 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008732 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8733 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8734 * if TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008735 *
8736 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008737 */
8738 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
Alan Viverettefe167872014-09-16 15:41:27 -07008739 if (attrs == null) {
8740 // Preserve behavior prior to removal of this API.
8741 throw new NullPointerException();
8742 }
8743
Alan Viveretteabda5482014-06-27 16:10:54 -07008744 // It's not safe to use this method from apps. The parameter 'attrs'
8745 // must have been obtained using the TextView filter array which is not
8746 // available to the SDK. As such, we grab a default TypedArray with the
8747 // right filter instead here.
8748 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8749 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008750 if (colors == null) {
Alan Viveretteabda5482014-06-27 16:10:54 -07008751 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8752 if (ap != 0) {
8753 final TypedArray appearance = context.obtainStyledAttributes(
8754 ap, R.styleable.TextAppearance);
8755 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008756 appearance.recycle();
8757 }
8758 }
Alan Viveretteabda5482014-06-27 16:10:54 -07008759 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008760
8761 return colors;
8762 }
8763
8764 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008765 * Returns the default color from the TextView_textColor attribute from the
8766 * AttributeSet, if set, or the default color from the
8767 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8768 * TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008769 *
8770 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008771 */
Alan Viveretteabda5482014-06-27 16:10:54 -07008772 public static int getTextColor(Context context, TypedArray attrs, int def) {
8773 final ColorStateList colors = getTextColors(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008774 if (colors == null) {
8775 return def;
8776 } else {
8777 return colors.getDefaultColor();
8778 }
8779 }
8780
8781 @Override
8782 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
James Cook9201e792015-02-11 10:46:44 -08008783 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8784 // Handle Ctrl-only shortcuts.
Jeff Brownc1df9072010-12-21 16:38:50 -08008785 switch (keyCode) {
8786 case KeyEvent.KEYCODE_A:
8787 if (canSelectText()) {
8788 return onTextContextMenuItem(ID_SELECT_ALL);
8789 }
8790 break;
James Cookf59152c2015-02-26 18:03:58 -08008791 case KeyEvent.KEYCODE_Z:
8792 if (canUndo()) {
8793 return onTextContextMenuItem(ID_UNDO);
8794 }
8795 break;
Jeff Brownc1df9072010-12-21 16:38:50 -08008796 case KeyEvent.KEYCODE_X:
8797 if (canCut()) {
8798 return onTextContextMenuItem(ID_CUT);
8799 }
8800 break;
8801 case KeyEvent.KEYCODE_C:
8802 if (canCopy()) {
8803 return onTextContextMenuItem(ID_COPY);
8804 }
8805 break;
8806 case KeyEvent.KEYCODE_V:
8807 if (canPaste()) {
8808 return onTextContextMenuItem(ID_PASTE);
8809 }
8810 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008811 }
James Cook9201e792015-02-11 10:46:44 -08008812 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8813 // Handle Ctrl-Shift shortcuts.
8814 switch (keyCode) {
James Cookf59152c2015-02-26 18:03:58 -08008815 case KeyEvent.KEYCODE_Z:
8816 if (canRedo()) {
8817 return onTextContextMenuItem(ID_REDO);
8818 }
8819 break;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09008820 case KeyEvent.KEYCODE_V:
8821 if (canPaste()) {
8822 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8823 }
James Cook9201e792015-02-11 10:46:44 -08008824 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008825 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008826 return super.onKeyShortcut(keyCode, event);
8827 }
8828
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008829 /**
8830 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8831 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07008832 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8833 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008834 */
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01008835 boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008836 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008837 }
8838
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008839 /**
8840 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8841 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008842 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008843 * See also {@link #canSelectText()}.
8844 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008845 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008846 // prepareCursorController() relies on this method.
8847 // If you change this condition, make sure prepareCursorController is called anywhere
8848 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008849 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008850 return isTextEditable() ||
8851 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008852 }
8853
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008854 private Locale getTextServicesLocale(boolean allowNullLocale) {
8855 // Start fetching the text services locale asynchronously.
8856 updateTextServicesLocaleAsync();
8857 // If !allowNullLocale and there is no cached text services locale, just return the default
8858 // locale.
8859 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8860 : mCurrentSpellCheckerLocaleCache;
8861 }
8862
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008863 /**
8864 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008865 * Caveat: This method may not return the latest text services locale, but this should be
8866 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008867 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008868 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008869 * in this TextView, based on the current spell checker settings,
8870 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008871 * Please note that a word iterator in this TextView is different from another word iterator
8872 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008873 * @hide
8874 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008875 // TODO: Support multi-locale
8876 // TODO: Update the text services locale immediately after the keyboard locale is switched
8877 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008878 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008879 return getTextServicesLocale(false /* allowNullLocale */);
8880 }
8881
8882 /**
Andrei Stingaceanub1891b32015-06-19 16:44:37 +01008883 * @return true if this TextView is specialized for showing and interacting with the extracted
8884 * text in a full-screen input method.
8885 * @hide
8886 */
8887 public boolean isInExtractedMode() {
8888 return false;
8889 }
8890
8891 /**
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008892 * This is a temporary method. Future versions may support multi-locale text.
8893 * Caveat: This method may not return the latest spell checker locale, but this should be
8894 * acceptable and it's more important to make this method asynchronous.
8895 *
8896 * @return The locale that should be used for a spell checker in this TextView,
8897 * based on the current spell checker settings, the current IME's locale, or the system default
8898 * locale.
8899 * @hide
8900 */
8901 public Locale getSpellCheckerLocale() {
8902 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008903 }
8904
8905 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008906 // AsyncTask.execute() uses a serial executor which means we don't have
8907 // to lock around updateTextServicesLocaleLocked() to prevent it from
8908 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008909 AsyncTask.execute(new Runnable() {
8910 @Override
8911 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008912 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008913 }
8914 });
8915 }
8916
8917 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008918 final TextServicesManager textServicesManager = (TextServicesManager)
8919 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8920 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008921 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008922 if (subtype != null) {
Yohei Yukawa658c29e2015-12-04 14:43:01 -08008923 locale = subtype.getLocaleObject();
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008924 } else {
8925 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008926 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008927 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008928 }
8929
8930 void onLocaleChanged() {
8931 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008932 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008933 }
8934
8935 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008936 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8937 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008938 * @hide
8939 */
8940 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008941 if (mEditor != null) {
8942 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008943 } else {
8944 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008945 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008946 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008947
Alan Viverettea54956a2015-01-07 16:05:02 -08008948 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008949 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008950 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8951 super.onPopulateAccessibilityEventInternal(event);
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008952
Alan Viverette798253e2015-08-27 14:53:48 -04008953 final CharSequence text = getTextForAccessibility();
8954 if (!TextUtils.isEmpty(text)) {
8955 event.getText().add(text);
svetoslavganov75986cf2009-05-14 22:28:01 -07008956 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008957 }
8958
alanv7d624192012-05-21 14:23:17 -07008959 /**
8960 * @return true if the user has explicitly allowed accessibility services
8961 * to speak passwords.
8962 */
8963 private boolean shouldSpeakPasswordsForAccessibility() {
Alan Viveretteb6e0cb92014-11-24 15:13:43 -08008964 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
Alan Viverette97524282014-12-02 16:24:24 -08008965 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8966 UserHandle.USER_CURRENT_OR_SELF) == 1);
alanv7d624192012-05-21 14:23:17 -07008967 }
8968
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008969 @Override
8970 public CharSequence getAccessibilityClassName() {
8971 return TextView.class.getName();
8972 }
8973
8974 @Override
Dianne Hackborn49b043f2015-05-07 14:21:38 -07008975 public void onProvideStructure(ViewStructure structure) {
8976 super.onProvideStructure(structure);
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07008977 final boolean isPassword = hasPasswordTransformationMethod()
8978 || isPasswordInputType(getInputType());
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008979 if (!isPassword) {
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07008980 if (mLayout == null) {
8981 assumeLayout();
8982 }
8983 Layout layout = mLayout;
8984 final int lineCount = layout.getLineCount();
8985 if (lineCount <= 1) {
8986 // Simple case: this is a single line.
8987 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8988 } else {
8989 // Complex case: multi-line, could be scrolled or within a scroll container
8990 // so some lines are not visible.
8991 final int[] tmpCords = new int[2];
8992 getLocationInWindow(tmpCords);
8993 final int topWindowLocation = tmpCords[1];
8994 View root = this;
8995 ViewParent viewParent = getParent();
8996 while (viewParent instanceof View) {
8997 root = (View) viewParent;
8998 viewParent = root.getParent();
8999 }
9000 final int windowHeight = root.getHeight();
9001 final int topLine;
9002 final int bottomLine;
9003 if (topWindowLocation >= 0) {
9004 // The top of the view is fully within its window; start text at line 0.
9005 topLine = getLineAtCoordinateUnclamped(0);
9006 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
9007 } else {
9008 // The top of hte window has scrolled off the top of the window; figure out
9009 // the starting line for this.
9010 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
9011 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
9012 }
9013 // We want to return some contextual lines above/below the lines that are
9014 // actually visible.
9015 int expandedTopLine = topLine - (bottomLine-topLine)/2;
9016 if (expandedTopLine < 0) {
9017 expandedTopLine = 0;
9018 }
9019 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
9020 if (expandedBottomLine >= lineCount) {
9021 expandedBottomLine = lineCount-1;
9022 }
9023 // Convert lines into character offsets.
9024 int expandedTopChar = layout.getLineStart(expandedTopLine);
9025 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
9026 // Take into account selection -- if there is a selection, we need to expand
9027 // the text we are returning to include that selection.
9028 final int selStart = getSelectionStart();
9029 final int selEnd = getSelectionEnd();
9030 if (selStart < selEnd) {
9031 if (selStart < expandedTopChar) {
9032 expandedTopChar = selStart;
9033 }
9034 if (selEnd > expandedBottomChar) {
9035 expandedBottomChar = selEnd;
9036 }
9037 }
9038 // Get the text and trim it to the range we are reporting.
9039 CharSequence text = getText();
9040 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9041 text = text.subSequence(expandedTopChar, expandedBottomChar);
9042 }
9043 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
9044 final int[] lineOffsets = new int[bottomLine-topLine+1];
9045 final int[] lineBaselines = new int[bottomLine-topLine+1];
9046 final int baselineOffset = getBaselineOffset();
9047 for (int i=topLine; i<=bottomLine; i++) {
9048 lineOffsets[i-topLine] = layout.getLineStart(i);
9049 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
9050 }
9051 structure.setTextLines(lineOffsets, lineBaselines);
9052 }
James Cook5cfaae42015-05-28 15:52:44 -07009053
9054 // Extract style information that applies to the TextView as a whole.
9055 int style = 0;
9056 int typefaceStyle = getTypefaceStyle();
9057 if ((typefaceStyle & Typeface.BOLD) != 0) {
9058 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9059 }
9060 if ((typefaceStyle & Typeface.ITALIC) != 0) {
9061 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9062 }
9063
9064 // Global styles can also be set via TextView.setPaintFlags().
9065 int paintFlags = mTextPaint.getFlags();
9066 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9067 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9068 }
9069 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9070 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9071 }
9072 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9073 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9074 }
9075
9076 // TextView does not have its own text background color. A background is either part
9077 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9078 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9079 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08009080 }
Dianne Hackborna83ce1d2015-03-11 15:16:13 -07009081 structure.setHint(getHint());
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08009082 }
9083
Alan Viverettea54956a2015-01-07 16:05:02 -08009084 /** @hide */
Svetoslav Ganov30401322011-05-12 18:53:45 -07009085 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009086 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9087 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov30401322011-05-12 18:53:45 -07009088
9089 final boolean isPassword = hasPasswordTransformationMethod();
9090 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07009091
9092 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9093 event.setFromIndex(Selection.getSelectionStart(mText));
9094 event.setToIndex(Selection.getSelectionEnd(mText));
9095 event.setItemCount(mText.length());
9096 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07009097 }
9098
Alan Viverettea54956a2015-01-07 16:05:02 -08009099 /** @hide */
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009100 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009101 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9102 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009103
9104 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08009105 info.setPassword(isPassword);
Alan Viverette798253e2015-08-27 14:53:48 -04009106 info.setText(getTextForAccessibility());
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009107
Svetoslavbcc46a02013-02-06 11:56:00 -08009108 if (mBufferType == BufferType.EDITABLE) {
9109 info.setEditable(true);
9110 }
9111
Svetoslav6254f482013-06-04 17:22:14 -07009112 if (mEditor != null) {
9113 info.setInputType(mEditor.mInputType);
Alan Viverette5b2081d2013-08-28 10:43:07 -07009114
9115 if (mEditor.mError != null) {
9116 info.setContentInvalid(true);
Alan Viverettefccbff52014-07-07 15:06:14 -07009117 info.setError(mEditor.mError);
Alan Viverette5b2081d2013-08-28 10:43:07 -07009118 }
Svetoslav6254f482013-06-04 17:22:14 -07009119 }
9120
Svetoslavdb7da0e2013-04-22 18:34:02 -07009121 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009122 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9123 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9124 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9125 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9126 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9127 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9128 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009129 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009130 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07009131
Svetoslav7c512842013-01-30 23:02:08 -08009132 if (isFocused()) {
Svetoslav7c512842013-01-30 23:02:08 -08009133 if (canCopy()) {
9134 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9135 }
9136 if (canPaste()) {
9137 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9138 }
9139 if (canCut()) {
9140 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9141 }
Abodunrinwa Toki0e5c6032015-06-26 14:11:21 -07009142 if (canShare()) {
9143 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9144 ACCESSIBILITY_ACTION_SHARE,
9145 getResources().getString(com.android.internal.R.string.share)));
9146 }
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009147 if (canProcessText()) { // also implies mEditor is not null.
9148 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9149 }
Svetoslav7c512842013-01-30 23:02:08 -08009150 }
Alan Viverette058ac7c2013-08-19 16:44:30 -07009151
Alan Viverette029942f2014-08-12 14:55:56 -07009152 // Check for known input filter types.
9153 final int numFilters = mFilters.length;
9154 for (int i = 0; i < numFilters; i++) {
9155 final InputFilter filter = mFilters[i];
9156 if (filter instanceof InputFilter.LengthFilter) {
9157 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9158 }
9159 }
9160
Alan Viverette058ac7c2013-08-19 16:44:30 -07009161 if (!isSingleLine()) {
9162 info.setMultiLine(true);
9163 }
Svetoslav7c512842013-01-30 23:02:08 -08009164 }
9165
Alan Viverettecd305ae2014-12-12 14:13:24 -08009166 /**
9167 * Performs an accessibility action after it has been offered to the
9168 * delegate.
9169 *
9170 * @hide
9171 */
Svetoslav7c512842013-01-30 23:02:08 -08009172 @Override
Alan Viverettecd305ae2014-12-12 14:13:24 -08009173 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009174 if (mEditor != null
9175 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9176 return true;
9177 }
Svetoslav7c512842013-01-30 23:02:08 -08009178 switch (action) {
Alan Viveretteb6877882014-09-07 15:49:49 -07009179 case AccessibilityNodeInfo.ACTION_CLICK: {
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009180 return performAccessibilityActionClick(arguments);
Alan Viveretteb6877882014-09-07 15:49:49 -07009181 }
Svetoslav7c512842013-01-30 23:02:08 -08009182 case AccessibilityNodeInfo.ACTION_COPY: {
9183 if (isFocused() && canCopy()) {
9184 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08009185 return true;
9186 }
9187 }
9188 } return false;
9189 case AccessibilityNodeInfo.ACTION_PASTE: {
9190 if (isFocused() && canPaste()) {
9191 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08009192 return true;
9193 }
9194 }
9195 } return false;
9196 case AccessibilityNodeInfo.ACTION_CUT: {
9197 if (isFocused() && canCut()) {
9198 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08009199 return true;
9200 }
9201 }
9202 } return false;
9203 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009204 ensureIterableTextForAccessibilitySelectable();
9205 CharSequence text = getIterableTextForAccessibility();
9206 if (text == null) {
9207 return false;
9208 }
9209 final int start = (arguments != null) ? arguments.getInt(
9210 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9211 final int end = (arguments != null) ? arguments.getInt(
9212 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9213 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9214 // No arguments clears the selection.
9215 if (start == end && end == -1) {
9216 Selection.removeSelection((Spannable) text);
9217 return true;
Svetoslav7c512842013-01-30 23:02:08 -08009218 }
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009219 if (start >= 0 && start <= end && end <= text.length()) {
9220 Selection.setSelection((Spannable) text, start, end);
9221 // Make sure selection mode is engaged.
9222 if (mEditor != null) {
9223 mEditor.startSelectionActionMode();
Svetoslav7c512842013-01-30 23:02:08 -08009224 }
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009225 return true;
Svetoslav7c512842013-01-30 23:02:08 -08009226 }
9227 }
9228 } return false;
Svet Ganov9e1c67e2014-10-14 08:53:33 -07009229 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9230 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9231 ensureIterableTextForAccessibilitySelectable();
Alan Viverettecd305ae2014-12-12 14:13:24 -08009232 return super.performAccessibilityActionInternal(action, arguments);
Svet Ganov9e1c67e2014-10-14 08:53:33 -07009233 }
Abodunrinwa Toki0e5c6032015-06-26 14:11:21 -07009234 case ACCESSIBILITY_ACTION_SHARE: {
9235 if (isFocused() && canShare()) {
9236 if (onTextContextMenuItem(ID_SHARE)) {
9237 return true;
9238 }
9239 }
9240 } return false;
Svetoslav7c512842013-01-30 23:02:08 -08009241 default: {
Alan Viverettecd305ae2014-12-12 14:13:24 -08009242 return super.performAccessibilityActionInternal(action, arguments);
Svetoslav7c512842013-01-30 23:02:08 -08009243 }
9244 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009245 }
9246
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009247 private boolean performAccessibilityActionClick(Bundle arguments) {
9248 boolean handled = false;
9249 boolean processed = false;
9250
9251 if (!isEnabled()) {
9252 return false;
9253 }
9254
9255 if (arguments != null && arguments.containsKey(
9256 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT)) {
9257 int spanIndex = arguments.getInt(
9258 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT, -1);
9259 if (spanIndex >= 0 && hasSpannableText()) {
9260 ClickableSpan[] spans = ((Spannable) mText).getSpans(0,
9261 mText.length(), ClickableSpan.class);
9262 if (spans != null && spans.length > spanIndex && spans[spanIndex] != null) {
9263 // Simulate View.onTouchEvent for an ACTION_UP event
9264 if (isFocusable() && !isFocused()) {
9265 requestFocus();
9266 }
9267 spans[spanIndex].onClick(this);
9268 handled = true;
9269 }
9270 }
9271 processed = true;
9272 }
9273
9274 if (!processed && arguments != null && arguments.containsKey(
9275 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT)) {
9276 int characterIndex = arguments.getInt(
9277 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT, -1);
9278 if (characterIndex >= 0 && hasSpannableText()) {
9279 ClickableSpan[] spans = ((Spannable) mText).getSpans(characterIndex,
9280 characterIndex, ClickableSpan.class);
9281 // click only on the first span to keep parity with onTouch() implementation
9282 if (spans != null && spans.length > 0 && spans[0] != null) {
9283 // Simulate View.onTouchEvent for an ACTION_UP event
9284 if (isFocusable() && !isFocused()) {
9285 requestFocus();
9286 }
9287 spans[0].onClick(this);
9288 handled = true;
9289 }
9290 }
9291 processed = true;
9292 }
9293
9294 if (!processed && (isClickable() || isLongClickable())) {
9295 // Simulate View.onTouchEvent for an ACTION_UP event
9296 if (isFocusable() && !isFocused()) {
9297 requestFocus();
9298 }
9299
9300 performClick();
9301 handled = true;
9302 }
9303
9304 // Show the IME, except when selecting in read-only text.
9305 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9306 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9307 final InputMethodManager imm = InputMethodManager.peekInstance();
9308 viewClicked(imm);
9309 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9310 handled |= imm.showSoftInput(this, 0);
9311 }
9312 }
9313
9314 return handled;
9315 }
9316
9317 private boolean hasSpannableText() {
9318 return mText != null && mText instanceof Spannable;
9319 }
9320
Alan Viverettea54956a2015-01-07 16:05:02 -08009321 /** @hide */
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009322 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009323 public void sendAccessibilityEventInternal(int eventType) {
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009324 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9325 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9326 }
9327
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009328 // Do not send scroll events since first they are not interesting for
9329 // accessibility and second such events a generated too frequently.
9330 // For details see the implementation of bringTextIntoView().
9331 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9332 return;
9333 }
Alan Viverettea54956a2015-01-07 16:05:02 -08009334 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009335 }
9336
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009337 /**
Alan Viverette798253e2015-08-27 14:53:48 -04009338 * Returns the text that should be exposed to accessibility services.
9339 * <p>
9340 * This approximates what is displayed visually. If the user has specified
9341 * that accessibility services should speak passwords, this method will
9342 * bypass any password transformation method and return unobscured text.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009343 *
Alan Viverette798253e2015-08-27 14:53:48 -04009344 * @return the text that should be exposed to accessibility services, may
9345 * be {@code null} if no text is set
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009346 */
Alan Viverette798253e2015-08-27 14:53:48 -04009347 @Nullable
9348 private CharSequence getTextForAccessibility() {
9349 // If the text is empty, we must be showing the hint text.
9350 if (TextUtils.isEmpty(mText)) {
9351 return mHint;
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009352 }
Alan Viverette798253e2015-08-27 14:53:48 -04009353
9354 // Check whether we need to bypass the transformation
9355 // method and expose unobscured text.
9356 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9357 return mText;
9358 }
9359
9360 // Otherwise, speak whatever text is being displayed.
9361 return mTransformed;
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009362 }
9363
svetoslavganov75986cf2009-05-14 22:28:01 -07009364 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9365 int fromIndex, int removedCount, int addedCount) {
9366 AccessibilityEvent event =
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009367 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
svetoslavganov75986cf2009-05-14 22:28:01 -07009368 event.setFromIndex(fromIndex);
9369 event.setRemovedCount(removedCount);
9370 event.setAddedCount(addedCount);
9371 event.setBeforeText(beforeText);
9372 sendAccessibilityEventUnchecked(event);
9373 }
9374
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009375 /**
9376 * Returns whether this text view is a current input method target. The
9377 * default implementation just checks with {@link InputMethodManager}.
9378 */
9379 public boolean isInputMethodTarget() {
9380 InputMethodManager imm = InputMethodManager.peekInstance();
9381 return imm != null && imm.isActive(this);
9382 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07009383
Gilles Debunned88876a2012-03-16 17:34:04 -07009384 static final int ID_SELECT_ALL = android.R.id.selectAll;
James Cookf59152c2015-02-26 18:03:58 -08009385 static final int ID_UNDO = android.R.id.undo;
9386 static final int ID_REDO = android.R.id.redo;
Gilles Debunned88876a2012-03-16 17:34:04 -07009387 static final int ID_CUT = android.R.id.cut;
9388 static final int ID_COPY = android.R.id.copy;
9389 static final int ID_PASTE = android.R.id.paste;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009390 static final int ID_SHARE = android.R.id.shareText;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009391 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
Clara Bayarrif84a9722015-03-02 16:09:09 +00009392 static final int ID_REPLACE = android.R.id.replaceText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009394 /**
9395 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07009396 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009397 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07009398 *
9399 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009400 */
9401 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009402 int min = 0;
9403 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07009404
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009405 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07009406 final int selStart = getSelectionStart();
9407 final int selEnd = getSelectionEnd();
9408
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009409 min = Math.max(0, Math.min(selStart, selEnd));
9410 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009411 }
9412
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009413 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08009414 case ID_SELECT_ALL:
Clara Bayarri01243ac2015-06-03 00:46:29 +01009415 // This starts an action mode if triggered from another action mode. Text is
9416 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9417 // true even if text is empty.
9418 boolean shouldRestartActionMode =
9419 mEditor != null && mEditor.mTextActionMode != null;
9420 stopTextActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07009421 selectAllText();
Clara Bayarri01243ac2015-06-03 00:46:29 +01009422 if (shouldRestartActionMode) {
9423 mEditor.startSelectionActionMode();
9424 }
Jeff Brownc1df9072010-12-21 16:38:50 -08009425 return true;
9426
James Cookf59152c2015-02-26 18:03:58 -08009427 case ID_UNDO:
9428 if (mEditor != null) {
9429 mEditor.undo();
9430 }
9431 return true; // Returns true even if nothing was undone.
9432
9433 case ID_REDO:
9434 if (mEditor != null) {
9435 mEditor.redo();
9436 }
9437 return true; // Returns true even if nothing was undone.
9438
Jeff Brownc1df9072010-12-21 16:38:50 -08009439 case ID_PASTE:
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009440 paste(min, max, true /* withFormatting */);
9441 return true;
9442
9443 case ID_PASTE_AS_PLAIN_TEXT:
9444 paste(min, max, false /* withFormatting */);
Jeff Brownc1df9072010-12-21 16:38:50 -08009445 return true;
9446
9447 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009448 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01009449 deleteText_internal(min, max);
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009450 stopTextActionMode();
Jeff Brownc1df9072010-12-21 16:38:50 -08009451 return true;
9452
9453 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009454 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009455 stopTextActionMode();
Jeff Brownc1df9072010-12-21 16:38:50 -08009456 return true;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009457
Andrei Stingaceanueeb9afc2015-05-12 12:39:07 +01009458 case ID_REPLACE:
9459 if (mEditor != null) {
9460 mEditor.replace();
9461 }
9462 return true;
9463
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009464 case ID_SHARE:
9465 shareSelectedText();
9466 return true;
Jeff Brownc1df9072010-12-21 16:38:50 -08009467 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009468 return false;
9469 }
9470
Gilles Debunned88876a2012-03-16 17:34:04 -07009471 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009472 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9473 }
9474
Gilles Debunnee15b3582010-06-16 15:17:21 -07009475 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009476 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009477 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07009478
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09009479 if (mEditor != null) {
9480 mEditor.mIsBeingLongClicked = true;
9481 }
9482
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009483 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009484 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009485 }
Gilles Debunnef170a342010-11-11 11:08:59 -08009486
Gilles Debunned88876a2012-03-16 17:34:04 -07009487 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009488 handled |= mEditor.performLongClick(handled);
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09009489 mEditor.mIsBeingLongClicked = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07009490 }
9491
Gilles Debunne9f102ca2012-02-28 11:15:54 -08009492 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009493 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07009494 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009496
Gilles Debunne299733e2011-02-07 17:11:41 -08009497 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009498 }
9499
Gilles Debunne60e21862012-01-30 15:04:14 -08009500 @Override
9501 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9502 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08009503 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009504 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08009505 }
9506 }
9507
9508 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009509 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9510 * by the IME or by the spell checker as the user types. This is done by adding
9511 * {@link SuggestionSpan}s to the text.
9512 *
9513 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9514 * user asks for them on these parts of the text. This value depends on the inputType of this
9515 * TextView.
9516 *
9517 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9518 *
9519 * In addition, the type variation must be one of
9520 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9521 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9522 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9523 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9524 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9525 *
9526 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9527 *
9528 * @return true if the suggestions popup window is enabled, based on the inputType.
9529 */
9530 public boolean isSuggestionsEnabled() {
9531 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009532 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9533 return false;
9534 }
9535 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009536
Gilles Debunne2d373a12012-04-20 15:32:19 -07009537 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08009538 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9539 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9540 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9541 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9542 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9543 }
9544
9545 /**
9546 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9547 * selection is initiated in this View.
9548 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009549 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009550 * Paste, Replace and Share actions, depending on what this View supports.
Gilles Debunne60e21862012-01-30 15:04:14 -08009551 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009552 * <p>A custom implementation can add new entries in the default menu in its
9553 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9554 * method. The default actions can also be removed from the menu using
James Cookd2026682015-03-03 14:40:14 -08009555 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009556 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9557 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
Gilles Debunne60e21862012-01-30 15:04:14 -08009558 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009559 * <p>Returning false from
9560 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9561 * will prevent the action mode from being started.
Gilles Debunne60e21862012-01-30 15:04:14 -08009562 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009563 * <p>Action click events should be handled by the custom implementation of
9564 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9565 * android.view.MenuItem)}.
Gilles Debunne60e21862012-01-30 15:04:14 -08009566 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009567 * <p>Note that text selection mode is not started when a TextView receives focus and the
Gilles Debunne60e21862012-01-30 15:04:14 -08009568 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9569 * that case, to allow for quick replacement.
9570 */
9571 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07009572 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07009573 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009574 }
9575
9576 /**
9577 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9578 *
9579 * @return The current custom selection callback.
9580 */
9581 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009582 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009583 }
9584
9585 /**
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009586 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9587 * insertion is initiated in this View.
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009588 * The standard implementation populates the menu with a subset of Select All,
9589 * Paste and Replace actions, depending on what this View supports.
9590 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009591 * <p>A custom implementation can add new entries in the default menu in its
9592 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9593 * android.view.Menu)} method. The default actions can also be removed from the menu using
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009594 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009595 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009596 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009597 * <p>Returning false from
9598 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9599 * android.view.Menu)} will prevent the action mode from being started.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009600 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009601 * <p>Action click events should be handled by the custom implementation of
9602 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9603 * android.view.MenuItem)}.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009604 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009605 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9606 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009607 */
9608 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9609 createEditorIfNeeded();
9610 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9611 }
9612
9613 /**
9614 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9615 *
9616 * @return The current custom insertion callback.
9617 */
9618 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9619 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9620 }
9621
9622 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009623 * @hide
9624 */
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009625 protected void stopTextActionMode() {
James Cookf59152c2015-02-26 18:03:58 -08009626 if (mEditor != null) {
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009627 mEditor.stopTextActionMode();
James Cookf59152c2015-02-26 18:03:58 -08009628 }
9629 }
9630
9631 boolean canUndo() {
9632 return mEditor != null && mEditor.canUndo();
9633 }
9634
9635 boolean canRedo() {
9636 return mEditor != null && mEditor.canRedo();
Gilles Debunned88876a2012-03-16 17:34:04 -07009637 }
9638
9639 boolean canCut() {
9640 if (hasPasswordTransformationMethod()) {
9641 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009642 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009643
Gilles Debunne2d373a12012-04-20 15:32:19 -07009644 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9645 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009646 return true;
9647 }
9648
9649 return false;
9650 }
9651
9652 boolean canCopy() {
9653 if (hasPasswordTransformationMethod()) {
9654 return false;
9655 }
9656
Victoria Leased849f542014-01-15 15:15:03 -08009657 if (mText.length() > 0 && hasSelection() && mEditor != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009658 return true;
9659 }
9660
9661 return false;
9662 }
9663
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009664 boolean canShare() {
Adam Powellbd918aad2016-01-21 10:49:39 -08009665 return canCopy() && isDeviceProvisioned();
9666 }
9667
9668 boolean isDeviceProvisioned() {
9669 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
9670 mDeviceProvisionedState = Settings.Global.getInt(
9671 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
9672 ? DEVICE_PROVISIONED_YES
9673 : DEVICE_PROVISIONED_NO;
9674 }
9675 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009676 }
9677
Gilles Debunned88876a2012-03-16 17:34:04 -07009678 boolean canPaste() {
9679 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07009680 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07009681 getSelectionStart() >= 0 &&
9682 getSelectionEnd() >= 0 &&
9683 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9684 hasPrimaryClip());
9685 }
9686
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009687 boolean canProcessText() {
9688 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9689 || hasPasswordTransformationMethod()) {
9690 return false;
9691 }
9692
9693 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9694 return true;
9695 }
9696
9697 return false;
9698 }
9699
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01009700 boolean canSelectAllText() {
Clara Bayarri3b69fd82015-06-03 21:52:02 +01009701 return canSelectText() && !hasPasswordTransformationMethod()
9702 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01009703 }
9704
Gilles Debunned88876a2012-03-16 17:34:04 -07009705 boolean selectAllText() {
Seigo Nonakabb6a62c2015-03-31 21:59:30 +09009706 // Need to hide insert point cursor controller before settings selection, otherwise insert
9707 // point cursor controller obtains cursor update event and update cursor with cancelling
9708 // selection.
9709 if (mEditor != null) {
9710 mEditor.hideInsertionPointCursorController();
9711 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009712 final int length = mText.length();
9713 Selection.setSelection((Spannable) mText, 0, length);
9714 return length > 0;
9715 }
9716
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009717 void replaceSelectionWithText(CharSequence text) {
9718 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9719 }
9720
Gilles Debunned88876a2012-03-16 17:34:04 -07009721 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009722 * Paste clipboard content between min and max positions.
9723 */
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009724 private void paste(int min, int max, boolean withFormatting) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009725 ClipboardManager clipboard =
9726 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9727 ClipData clip = clipboard.getPrimaryClip();
9728 if (clip != null) {
9729 boolean didFirst = false;
9730 for (int i=0; i<clip.getItemCount(); i++) {
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009731 final CharSequence paste;
9732 if (withFormatting) {
9733 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9734 } else {
9735 // Get an item as text and remove all spans by toString().
9736 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9737 paste = (text instanceof Spanned) ? text.toString() : text;
9738 }
Gilles Debunne60e21862012-01-30 15:04:14 -08009739 if (paste != null) {
9740 if (!didFirst) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009741 Selection.setSelection((Spannable) mText, max);
9742 ((Editable) mText).replace(min, max, paste);
9743 didFirst = true;
9744 } else {
9745 ((Editable) mText).insert(getSelectionEnd(), "\n");
9746 ((Editable) mText).insert(getSelectionEnd(), paste);
9747 }
9748 }
9749 }
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009750 stopTextActionMode();
Andrei Stingaceanu77b9c382015-05-06 13:25:19 +01009751 sLastCutCopyOrTextChangedTime = 0;
Gilles Debunne60e21862012-01-30 15:04:14 -08009752 }
9753 }
9754
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009755 private void shareSelectedText() {
9756 String selectedText = getSelectedText();
9757 if (selectedText != null && !selectedText.isEmpty()) {
9758 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9759 sharingIntent.setType("text/plain");
9760 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9761 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9762 getContext().startActivity(Intent.createChooser(sharingIntent, null));
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009763 stopTextActionMode();
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009764 }
9765 }
9766
Gilles Debunne60e21862012-01-30 15:04:14 -08009767 private void setPrimaryClip(ClipData clip) {
9768 ClipboardManager clipboard = (ClipboardManager) getContext().
9769 getSystemService(Context.CLIPBOARD_SERVICE);
9770 clipboard.setPrimaryClip(clip);
Andrei Stingaceanu77b9c382015-05-06 13:25:19 +01009771 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
Gilles Debunne60e21862012-01-30 15:04:14 -08009772 }
9773
Gilles Debunne60e21862012-01-30 15:04:14 -08009774 /**
9775 * Get the character offset closest to the specified absolute position. A typical use case is to
9776 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9777 *
9778 * @param x The horizontal absolute position of a point on screen
9779 * @param y The vertical absolute position of a point on screen
9780 * @return the character offset for the character whose position is closest to the specified
9781 * position. Returns -1 if there is no layout.
9782 */
9783 public int getOffsetForPosition(float x, float y) {
9784 if (getLayout() == null) return -1;
9785 final int line = getLineAtCoordinate(y);
9786 final int offset = getOffsetAtCoordinate(line, x);
9787 return offset;
9788 }
9789
Gilles Debunned88876a2012-03-16 17:34:04 -07009790 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009791 x -= getTotalPaddingLeft();
9792 // Clamp the position to inside of the view.
9793 x = Math.max(0.0f, x);
9794 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9795 x += getScrollX();
9796 return x;
9797 }
9798
Gilles Debunned88876a2012-03-16 17:34:04 -07009799 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009800 y -= getTotalPaddingTop();
9801 // Clamp the position to inside of the view.
9802 y = Math.max(0.0f, y);
9803 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9804 y += getScrollY();
9805 return getLayout().getLineForVertical((int) y);
9806 }
9807
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009808 int getLineAtCoordinateUnclamped(float y) {
9809 y -= getTotalPaddingTop();
9810 y += getScrollY();
9811 return getLayout().getLineForVertical((int) y);
9812 }
9813
Mady Mellor2ff2cd82015-03-02 10:37:01 -08009814 int getOffsetAtCoordinate(int line, float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009815 x = convertToLocalHorizontalCoordinate(x);
9816 return getLayout().getOffsetForHorizontal(line, x);
9817 }
9818
Gilles Debunne60e21862012-01-30 15:04:14 -08009819 @Override
9820 public boolean onDragEvent(DragEvent event) {
9821 switch (event.getAction()) {
9822 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009823 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08009824
9825 case DragEvent.ACTION_DRAG_ENTERED:
9826 TextView.this.requestFocus();
9827 return true;
9828
9829 case DragEvent.ACTION_DRAG_LOCATION:
9830 final int offset = getOffsetForPosition(event.getX(), event.getY());
9831 Selection.setSelection((Spannable)mText, offset);
9832 return true;
9833
9834 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009835 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08009836 return true;
9837
9838 case DragEvent.ACTION_DRAG_ENDED:
9839 case DragEvent.ACTION_DRAG_EXITED:
9840 default:
9841 return true;
9842 }
9843 }
9844
Gilles Debunne60e21862012-01-30 15:04:14 -08009845 boolean isInBatchEditMode() {
9846 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009847 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08009848 if (ims != null) {
9849 return ims.mBatchEditNesting > 0;
9850 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07009851 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08009852 }
9853
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009854 @Override
9855 public void onRtlPropertiesChanged(int layoutDirection) {
9856 super.onRtlPropertiesChanged(layoutDirection);
9857
Keisuke Kuroyanagi17b0bda2015-06-19 15:24:07 +09009858 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9859 if (mTextDir != newTextDir) {
9860 mTextDir = newTextDir;
9861 if (mLayout != null) {
9862 checkForRelayout();
9863 }
Fabrice Di Meglio22228fe2014-01-06 16:30:43 -08009864 }
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009865 }
9866
Selim Cinek4fb12d32015-11-19 18:10:48 -08009867 /**
9868 * @hide
9869 */
9870 protected TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08009871 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08009872 // passwords fields should be LTR
9873 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009874 }
9875
9876 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07009877 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08009878
9879 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07009880 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009881 default:
9882 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009883 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08009884 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08009885 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009886 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009887 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009888 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009889 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009890 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009891 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009892 return TextDirectionHeuristics.LOCALE;
Roozbeh Pournaderb51222a2015-04-13 14:33:40 -07009893 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9894 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9895 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9896 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009897 }
9898 }
9899
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009900 /**
9901 * @hide
9902 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009903 @Override
9904 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009905 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009906 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009907 return;
9908 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009909 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08009910
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009911 // Resolve drawables
9912 if (mDrawables != null) {
Alan Viverette189d4f52015-12-16 16:06:23 -05009913 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
9914 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
9915 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
9916 applyCompoundDrawableTint();
9917 }
9918 }
9919 }
9920
9921 /**
9922 * Prepares a drawable for display by propagating layout direction and
9923 * drawable state.
9924 *
9925 * @param dr the drawable to prepare
9926 */
9927 private void prepareDrawableForDisplay(@Nullable Drawable dr) {
9928 if (dr == null) {
9929 return;
9930 }
9931
9932 dr.setLayoutDirection(getLayoutDirection());
9933
9934 if (dr.isStateful()) {
9935 dr.setState(getDrawableState());
9936 dr.jumpToCurrentState();
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009937 }
9938 }
9939
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07009940 /**
9941 * @hide
9942 */
Gilles Debunne60e21862012-01-30 15:04:14 -08009943 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009944 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009945 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08009946 }
9947
9948 /**
9949 * @hide
9950 */
9951 protected void viewClicked(InputMethodManager imm) {
9952 if (imm != null) {
9953 imm.viewClicked(this);
9954 }
9955 }
9956
9957 /**
9958 * Deletes the range of text [start, end[.
9959 * @hide
9960 */
9961 protected void deleteText_internal(int start, int end) {
9962 ((Editable) mText).delete(start, end);
9963 }
9964
9965 /**
9966 * Replaces the range of text [start, end[ by replacement text
9967 * @hide
9968 */
9969 protected void replaceText_internal(int start, int end, CharSequence text) {
9970 ((Editable) mText).replace(start, end, text);
9971 }
9972
9973 /**
9974 * Sets a span on the specified range of text
9975 * @hide
9976 */
9977 protected void setSpan_internal(Object span, int start, int end, int flags) {
9978 ((Editable) mText).setSpan(span, start, end, flags);
9979 }
9980
9981 /**
9982 * Moves the cursor to the specified offset position in text
9983 * @hide
9984 */
9985 protected void setCursorPosition_internal(int start, int end) {
9986 Selection.setSelection(((Editable) mText), start, end);
9987 }
9988
9989 /**
9990 * An Editor should be created as soon as any of the editable-specific fields (grouped
9991 * inside the Editor object) is assigned to a non-default value.
9992 * This method will create the Editor if needed.
9993 *
9994 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9995 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9996 * Editor for backward compatibility, as soon as one of these fields is assigned.
9997 *
9998 * Also note that for performance reasons, the mEditor is created when needed, but not
9999 * reset when no more edit-specific fields are needed.
10000 */
Gilles Debunne5fae9962012-05-08 14:53:20 -070010001 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -080010002 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -070010003 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -080010004 }
10005 }
10006
Gilles Debunne60e21862012-01-30 15:04:14 -080010007 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010008 * @hide
10009 */
10010 @Override
10011 public CharSequence getIterableTextForAccessibility() {
Svet Ganov9e1c67e2014-10-14 08:53:33 -070010012 return mText;
10013 }
10014
10015 private void ensureIterableTextForAccessibilitySelectable() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010016 if (!(mText instanceof Spannable)) {
10017 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010018 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010019 }
10020
10021 /**
10022 * @hide
10023 */
10024 @Override
10025 public TextSegmentIterator getIteratorForGranularity(int granularity) {
10026 switch (granularity) {
10027 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
10028 Spannable text = (Spannable) getIterableTextForAccessibility();
10029 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10030 AccessibilityIterators.LineTextSegmentIterator iterator =
10031 AccessibilityIterators.LineTextSegmentIterator.getInstance();
10032 iterator.initialize(text, getLayout());
10033 return iterator;
10034 }
10035 } break;
10036 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
10037 Spannable text = (Spannable) getIterableTextForAccessibility();
10038 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10039 AccessibilityIterators.PageTextSegmentIterator iterator =
10040 AccessibilityIterators.PageTextSegmentIterator.getInstance();
10041 iterator.initialize(this);
10042 return iterator;
10043 }
10044 } break;
10045 }
10046 return super.getIteratorForGranularity(granularity);
10047 }
10048
10049 /**
10050 * @hide
10051 */
10052 @Override
Svetoslav7c512842013-01-30 23:02:08 -080010053 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010054 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -080010055 }
10056
10057 /**
10058 * @hide
10059 */
10060 public boolean isAccessibilitySelectionExtendable() {
10061 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010062 }
10063
10064 /**
10065 * @hide
10066 */
10067 @Override
Svetoslav7c512842013-01-30 23:02:08 -080010068 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010069 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -080010070 }
10071
10072 /**
10073 * @hide
10074 */
10075 @Override
10076 public void setAccessibilitySelection(int start, int end) {
10077 if (getAccessibilitySelectionStart() == start
10078 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010079 return;
10080 }
Svetoslavabad55d2013-05-07 18:49:51 -070010081 // Hide all selection controllers used for adjusting selection
10082 // since we are doing so explicitlty by other means and these
10083 // controllers interact with how selection behaves.
10084 if (mEditor != null) {
Mady Mellora2861452015-06-25 08:40:27 -070010085 mEditor.hideCursorAndSpanControllers();
Mady Mellor011a42d2015-06-24 17:27:21 -070010086 mEditor.stopTextActionMode();
Svetoslavabad55d2013-05-07 18:49:51 -070010087 }
Svetoslav7c512842013-01-30 23:02:08 -080010088 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -070010089 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -080010090 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010091 } else {
Svetoslav7c512842013-01-30 23:02:08 -080010092 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010093 }
10094 }
10095
Siva Velusamy94a6d152015-05-05 15:07:00 -070010096 /** @hide */
10097 @Override
10098 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10099 super.encodeProperties(stream);
10100
10101 TruncateAt ellipsize = getEllipsize();
10102 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10103 stream.addProperty("text:textSize", getTextSize());
10104 stream.addProperty("text:scaledTextSize", getScaledTextSize());
10105 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10106 stream.addProperty("text:selectionStart", getSelectionStart());
10107 stream.addProperty("text:selectionEnd", getSelectionEnd());
10108 stream.addProperty("text:curTextColor", mCurTextColor);
10109 stream.addProperty("text:text", mText == null ? null : mText.toString());
10110 stream.addProperty("text:gravity", mGravity);
10111 }
10112
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010113 /**
Gilles Debunne60e21862012-01-30 15:04:14 -080010114 * User interface state that is stored by TextView for implementing
10115 * {@link View#onSaveInstanceState}.
10116 */
10117 public static class SavedState extends BaseSavedState {
Siyamed Sinir02771192016-02-05 16:08:59 -080010118 int selStart = -1;
10119 int selEnd = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -080010120 CharSequence text;
10121 boolean frozenWithFocus;
10122 CharSequence error;
James Cookf59152c2015-02-26 18:03:58 -080010123 ParcelableParcel editorState; // Optional state from Editor.
Gilles Debunne60e21862012-01-30 15:04:14 -080010124
10125 SavedState(Parcelable superState) {
10126 super(superState);
10127 }
10128
10129 @Override
10130 public void writeToParcel(Parcel out, int flags) {
10131 super.writeToParcel(out, flags);
10132 out.writeInt(selStart);
10133 out.writeInt(selEnd);
10134 out.writeInt(frozenWithFocus ? 1 : 0);
10135 TextUtils.writeToParcel(text, out, flags);
10136
10137 if (error == null) {
10138 out.writeInt(0);
10139 } else {
10140 out.writeInt(1);
10141 TextUtils.writeToParcel(error, out, flags);
10142 }
James Cookf59152c2015-02-26 18:03:58 -080010143
10144 if (editorState == null) {
10145 out.writeInt(0);
10146 } else {
10147 out.writeInt(1);
10148 editorState.writeToParcel(out, flags);
10149 }
Gilles Debunne60e21862012-01-30 15:04:14 -080010150 }
10151
10152 @Override
10153 public String toString() {
10154 String str = "TextView.SavedState{"
10155 + Integer.toHexString(System.identityHashCode(this))
10156 + " start=" + selStart + " end=" + selEnd;
10157 if (text != null) {
10158 str += " text=" + text;
10159 }
10160 return str + "}";
10161 }
10162
10163 @SuppressWarnings("hiding")
10164 public static final Parcelable.Creator<SavedState> CREATOR
10165 = new Parcelable.Creator<SavedState>() {
10166 public SavedState createFromParcel(Parcel in) {
10167 return new SavedState(in);
10168 }
10169
10170 public SavedState[] newArray(int size) {
10171 return new SavedState[size];
10172 }
10173 };
10174
10175 private SavedState(Parcel in) {
10176 super(in);
10177 selStart = in.readInt();
10178 selEnd = in.readInt();
10179 frozenWithFocus = (in.readInt() != 0);
10180 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10181
10182 if (in.readInt() != 0) {
10183 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10184 }
James Cookf59152c2015-02-26 18:03:58 -080010185
10186 if (in.readInt() != 0) {
10187 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10188 }
Gilles Debunne60e21862012-01-30 15:04:14 -080010189 }
10190 }
10191
10192 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10193 private char[] mChars;
10194 private int mStart, mLength;
10195
10196 public CharWrapper(char[] chars, int start, int len) {
10197 mChars = chars;
10198 mStart = start;
10199 mLength = len;
10200 }
10201
10202 /* package */ void set(char[] chars, int start, int len) {
10203 mChars = chars;
10204 mStart = start;
10205 mLength = len;
10206 }
10207
10208 public int length() {
10209 return mLength;
10210 }
10211
10212 public char charAt(int off) {
10213 return mChars[off + mStart];
10214 }
10215
10216 @Override
10217 public String toString() {
10218 return new String(mChars, mStart, mLength);
10219 }
10220
10221 public CharSequence subSequence(int start, int end) {
10222 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10223 throw new IndexOutOfBoundsException(start + ", " + end);
10224 }
10225
10226 return new String(mChars, start + mStart, end - start);
10227 }
10228
10229 public void getChars(int start, int end, char[] buf, int off) {
10230 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10231 throw new IndexOutOfBoundsException(start + ", " + end);
10232 }
10233
10234 System.arraycopy(mChars, start + mStart, buf, off, end - start);
10235 }
10236
10237 public void drawText(Canvas c, int start, int end,
10238 float x, float y, Paint p) {
10239 c.drawText(mChars, start + mStart, end - start, x, y, p);
10240 }
10241
10242 public void drawTextRun(Canvas c, int start, int end,
Raph Levien051910b2014-06-15 18:25:29 -070010243 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010244 int count = end - start;
10245 int contextCount = contextEnd - contextStart;
10246 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -070010247 contextCount, x, y, isRtl, p);
Gilles Debunne60e21862012-01-30 15:04:14 -080010248 }
10249
10250 public float measureText(int start, int end, Paint p) {
10251 return p.measureText(mChars, start + mStart, end - start);
10252 }
10253
10254 public int getTextWidths(int start, int end, float[] widths, Paint p) {
10255 return p.getTextWidths(mChars, start + mStart, end - start, widths);
10256 }
10257
10258 public float getTextRunAdvances(int start, int end, int contextStart,
Raph Levien051910b2014-06-15 18:25:29 -070010259 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -080010260 Paint p) {
10261 int count = end - start;
10262 int contextCount = contextEnd - contextStart;
10263 return p.getTextRunAdvances(mChars, start + mStart, count,
Raph Levien051910b2014-06-15 18:25:29 -070010264 contextStart + mStart, contextCount, isRtl, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -080010265 advancesIndex);
10266 }
10267
Raph Levien051910b2014-06-15 18:25:29 -070010268 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
Gilles Debunne60e21862012-01-30 15:04:14 -080010269 int offset, int cursorOpt, Paint p) {
10270 int contextCount = contextEnd - contextStart;
10271 return p.getTextRunCursor(mChars, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -070010272 contextCount, dir, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -080010273 }
10274 }
10275
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010276 private static final class Marquee {
Gilles Debunne60e21862012-01-30 15:04:14 -080010277 // TODO: Add an option to configure this
10278 private static final float MARQUEE_DELTA_MAX = 0.07f;
10279 private static final int MARQUEE_DELAY = 1200;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010280 private static final int MARQUEE_DP_PER_SECOND = 30;
Gilles Debunne60e21862012-01-30 15:04:14 -080010281
10282 private static final byte MARQUEE_STOPPED = 0x0;
10283 private static final byte MARQUEE_STARTING = 0x1;
10284 private static final byte MARQUEE_RUNNING = 0x2;
10285
Gilles Debunne60e21862012-01-30 15:04:14 -080010286 private final WeakReference<TextView> mView;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010287 private final Choreographer mChoreographer;
Gilles Debunne60e21862012-01-30 15:04:14 -080010288
10289 private byte mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010290 private final float mPixelsPerSecond;
Gilles Debunne60e21862012-01-30 15:04:14 -080010291 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010292 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -080010293 private float mGhostStart;
10294 private float mGhostOffset;
10295 private float mFadeStop;
10296 private int mRepeatLimit;
10297
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010298 private float mScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010299 private long mLastAnimationMs;
Gilles Debunne60e21862012-01-30 15:04:14 -080010300
10301 Marquee(TextView v) {
10302 final float density = v.getContext().getResources().getDisplayMetrics().density;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010303 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
Gilles Debunne60e21862012-01-30 15:04:14 -080010304 mView = new WeakReference<TextView>(v);
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010305 mChoreographer = Choreographer.getInstance();
Gilles Debunne60e21862012-01-30 15:04:14 -080010306 }
10307
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010308 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10309 @Override
10310 public void doFrame(long frameTimeNanos) {
10311 tick();
Gilles Debunne60e21862012-01-30 15:04:14 -080010312 }
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010313 };
10314
10315 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10316 @Override
10317 public void doFrame(long frameTimeNanos) {
10318 mStatus = MARQUEE_RUNNING;
10319 mLastAnimationMs = mChoreographer.getFrameTime();
10320 tick();
10321 }
10322 };
10323
10324 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10325 @Override
10326 public void doFrame(long frameTimeNanos) {
10327 if (mStatus == MARQUEE_RUNNING) {
10328 if (mRepeatLimit >= 0) {
10329 mRepeatLimit--;
10330 }
10331 start(mRepeatLimit);
10332 }
10333 }
10334 };
Gilles Debunne60e21862012-01-30 15:04:14 -080010335
10336 void tick() {
10337 if (mStatus != MARQUEE_RUNNING) {
10338 return;
10339 }
10340
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010341 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010342
10343 final TextView textView = mView.get();
10344 if (textView != null && (textView.isFocused() || textView.isSelected())) {
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010345 long currentMs = mChoreographer.getFrameTime();
10346 long deltaMs = currentMs - mLastAnimationMs;
10347 mLastAnimationMs = currentMs;
10348 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10349 mScroll += deltaPx;
Gilles Debunne60e21862012-01-30 15:04:14 -080010350 if (mScroll > mMaxScroll) {
10351 mScroll = mMaxScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010352 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
Gilles Debunne60e21862012-01-30 15:04:14 -080010353 } else {
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010354 mChoreographer.postFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010355 }
10356 textView.invalidate();
10357 }
10358 }
10359
10360 void stop() {
10361 mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010362 mChoreographer.removeFrameCallback(mStartCallback);
10363 mChoreographer.removeFrameCallback(mRestartCallback);
10364 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010365 resetScroll();
10366 }
10367
10368 private void resetScroll() {
10369 mScroll = 0.0f;
10370 final TextView textView = mView.get();
10371 if (textView != null) textView.invalidate();
10372 }
10373
10374 void start(int repeatLimit) {
10375 if (repeatLimit == 0) {
10376 stop();
10377 return;
10378 }
10379 mRepeatLimit = repeatLimit;
10380 final TextView textView = mView.get();
10381 if (textView != null && textView.mLayout != null) {
10382 mStatus = MARQUEE_STARTING;
10383 mScroll = 0.0f;
10384 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10385 textView.getCompoundPaddingRight();
10386 final float lineWidth = textView.mLayout.getLineWidth(0);
10387 final float gap = textWidth / 3.0f;
10388 mGhostStart = lineWidth - textWidth + gap;
10389 mMaxScroll = mGhostStart + textWidth;
10390 mGhostOffset = lineWidth + gap;
10391 mFadeStop = lineWidth + textWidth / 6.0f;
10392 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10393
10394 textView.invalidate();
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010395 mChoreographer.postFrameCallback(mStartCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010396 }
10397 }
10398
10399 float getGhostOffset() {
10400 return mGhostOffset;
10401 }
10402
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010403 float getScroll() {
10404 return mScroll;
10405 }
10406
10407 float getMaxFadeScroll() {
10408 return mMaxFadeScroll;
10409 }
10410
Gilles Debunne60e21862012-01-30 15:04:14 -080010411 boolean shouldDrawLeftFade() {
10412 return mScroll <= mFadeStop;
10413 }
10414
10415 boolean shouldDrawGhost() {
10416 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10417 }
10418
10419 boolean isRunning() {
10420 return mStatus == MARQUEE_RUNNING;
10421 }
10422
10423 boolean isStopped() {
10424 return mStatus == MARQUEE_STOPPED;
10425 }
10426 }
10427
Gilles Debunne60e21862012-01-30 15:04:14 -080010428 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10429
10430 private CharSequence mBeforeText;
10431
Gilles Debunne60e21862012-01-30 15:04:14 -080010432 public void beforeTextChanged(CharSequence buffer, int start,
10433 int before, int after) {
10434 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10435 + " before=" + before + " after=" + after + ": " + buffer);
10436
10437 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -080010438 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10439 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010440 mBeforeText = buffer.toString();
10441 }
10442
10443 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10444 }
10445
Gilles Debunned88876a2012-03-16 17:34:04 -070010446 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010447 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10448 + " before=" + before + " after=" + after + ": " + buffer);
10449 TextView.this.handleTextChanged(buffer, start, before, after);
10450
Gilles Debunne60e21862012-01-30 15:04:14 -080010451 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10452 (isFocused() || isSelected() && isShown())) {
10453 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10454 mBeforeText = null;
10455 }
10456 }
10457
10458 public void afterTextChanged(Editable buffer) {
10459 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10460 TextView.this.sendAfterTextChanged(buffer);
10461
10462 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10463 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10464 }
10465 }
10466
Gilles Debunned88876a2012-03-16 17:34:04 -070010467 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010468 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10469 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10470 TextView.this.spanChange(buf, what, s, st, e, en);
10471 }
10472
10473 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10474 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10475 + " what=" + what + ": " + buf);
10476 TextView.this.spanChange(buf, what, -1, s, -1, e);
10477 }
10478
10479 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10480 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10481 + " what=" + what + ": " + buf);
10482 TextView.this.spanChange(buf, what, s, -1, e, -1);
10483 }
satoka67a3cf2011-09-07 17:14:03 +090010484 }
Mady Mellor138bc2f2015-03-05 15:15:25 +000010485}