blob: 95fcdc17964327ea6f4714d0a675bcab6a4bcc83 [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 /*
666 * Kick-start the font cache for the zygote process (to pay the cost of
667 * initializing freetype for our default font only once).
668 */
669 static {
670 Paint p = new Paint();
671 p.setAntiAlias(true);
672 // We don't care about the result, just the side-effect of measuring.
673 p.measureText("H");
674 }
675
676 /**
677 * Interface definition for a callback to be invoked when an action is
678 * performed on the editor.
679 */
680 public interface OnEditorActionListener {
681 /**
682 * Called when an action is being performed.
683 *
684 * @param v The view that was clicked.
685 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700686 * identifier you supplied, or {@link EditorInfo#IME_NULL
687 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800688 * being pressed.
689 * @param event If triggered by an enter key, this is the event;
690 * otherwise, this is null.
691 * @return Return true if you have consumed the action, else false.
692 */
693 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
694 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700695
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800696 public TextView(Context context) {
697 this(context, null);
698 }
699
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800700 public TextView(Context context, @Nullable AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 this(context, attrs, com.android.internal.R.attr.textViewStyle);
702 }
703
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800704 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
Alan Viverette617feb92013-09-09 18:09:13 -0700705 this(context, attrs, defStyleAttr, 0);
706 }
707
Gilles Debunnee15b3582010-06-16 15:17:21 -0700708 @SuppressWarnings("deprecation")
Scott Kennedy3a1fa102015-03-05 19:15:11 -0800709 public TextView(
710 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
Alan Viverette617feb92013-09-09 18:09:13 -0700711 super(context, attrs, defStyleAttr, defStyleRes);
712
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 mText = "";
714
Christopher Tate1373a8e2011-11-10 19:59:13 -0800715 final Resources res = getResources();
716 final CompatibilityInfo compat = res.getCompatibilityInfo();
717
Chris Craik6a49dde2015-05-12 10:28:14 -0700718 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800719 mTextPaint.density = res.getDisplayMetrics().density;
720 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800721
Chris Craik6a49dde2015-05-12 10:28:14 -0700722 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Gilles Debunne83051b82012-02-24 20:01:13 -0800723 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800726
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 mTransformation = null;
728
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729 int textColorHighlight = 0;
730 ColorStateList textColor = null;
731 ColorStateList textColorHint = null;
732 ColorStateList textColorLink = null;
733 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700734 String fontFamily = null;
Raph Levien42b30242015-01-29 12:49:19 -0800735 boolean fontFamilyExplicit = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800736 int typefaceIndex = -1;
737 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700738 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800739 int shadowcolor = 0;
740 float dx = 0, dy = 0, r = 0;
Raph Levien53c00772014-04-14 14:11:02 -0700741 boolean elegant = false;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400742 float letterSpacing = 0;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400743 String fontFeatureSettings = null;
Raph Levien39b4db72015-03-25 13:18:20 -0700744 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
Roozbeh Pournader95c7a132015-05-12 12:01:06 -0700745 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700747 final Resources.Theme theme = context.getTheme();
748
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800749 /*
750 * Look the appearance up without checking first if it exists because
751 * almost every TextView has one and it greatly simplifies the logic
752 * to be able to parse the appearance first and then let specific tags
753 * for this View override it.
754 */
Alan Viverette617feb92013-09-09 18:09:13 -0700755 TypedArray a = theme.obtainStyledAttributes(attrs,
756 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800757 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700758 int ap = a.getResourceId(
759 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
760 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800761 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700762 appearance = theme.obtainStyledAttributes(
763 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 }
765 if (appearance != null) {
766 int n = appearance.getIndexCount();
767 for (int i = 0; i < n; i++) {
768 int attr = appearance.getIndex(i);
769
770 switch (attr) {
771 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
772 textColorHighlight = appearance.getColor(attr, textColorHighlight);
773 break;
774
775 case com.android.internal.R.styleable.TextAppearance_textColor:
776 textColor = appearance.getColorStateList(attr);
777 break;
778
779 case com.android.internal.R.styleable.TextAppearance_textColorHint:
780 textColorHint = appearance.getColorStateList(attr);
781 break;
782
783 case com.android.internal.R.styleable.TextAppearance_textColorLink:
784 textColorLink = appearance.getColorStateList(attr);
785 break;
786
787 case com.android.internal.R.styleable.TextAppearance_textSize:
788 textSize = appearance.getDimensionPixelSize(attr, textSize);
789 break;
790
791 case com.android.internal.R.styleable.TextAppearance_typeface:
792 typefaceIndex = appearance.getInt(attr, -1);
793 break;
794
Raph Leviend570e892012-05-09 11:45:34 -0700795 case com.android.internal.R.styleable.TextAppearance_fontFamily:
796 fontFamily = appearance.getString(attr);
797 break;
798
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 case com.android.internal.R.styleable.TextAppearance_textStyle:
800 styleIndex = appearance.getInt(attr, -1);
801 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700802
803 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
804 allCaps = appearance.getBoolean(attr, false);
805 break;
Adam Powellac91df82013-02-14 13:48:47 -0800806
807 case com.android.internal.R.styleable.TextAppearance_shadowColor:
Raph Levien202d1ec2014-04-14 11:20:22 -0700808 shadowcolor = appearance.getInt(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800809 break;
810
811 case com.android.internal.R.styleable.TextAppearance_shadowDx:
Raph Levien202d1ec2014-04-14 11:20:22 -0700812 dx = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800813 break;
814
815 case com.android.internal.R.styleable.TextAppearance_shadowDy:
Raph Levien202d1ec2014-04-14 11:20:22 -0700816 dy = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800817 break;
818
819 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
Raph Levien202d1ec2014-04-14 11:20:22 -0700820 r = appearance.getFloat(attr, 0);
Adam Powellac91df82013-02-14 13:48:47 -0800821 break;
Raph Levien53c00772014-04-14 14:11:02 -0700822
823 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
824 elegant = appearance.getBoolean(attr, false);
825 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -0400826
827 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
828 letterSpacing = appearance.getFloat(attr, 0);
829 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -0400830
831 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
832 fontFeatureSettings = appearance.getString(attr);
833 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 }
835 }
836
837 appearance.recycle();
838 }
839
840 boolean editable = getDefaultEditable();
841 CharSequence inputMethod = null;
842 int numeric = 0;
843 CharSequence digits = null;
844 boolean phone = false;
845 boolean autotext = false;
846 int autocap = -1;
847 int buffertype = 0;
848 boolean selectallonfocus = false;
849 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700850 drawableBottom = null, drawableStart = null, drawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -0800851 ColorStateList drawableTint = null;
852 PorterDuff.Mode drawableTintMode = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 int drawablePadding = 0;
854 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700855 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800856 int maxlength = -1;
857 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700858 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800859 boolean password = false;
860 int inputType = EditorInfo.TYPE_NULL;
861
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700862 a = theme.obtainStyledAttributes(
Alan Viverette617feb92013-09-09 18:09:13 -0700863 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700864
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800865 int n = a.getIndexCount();
866 for (int i = 0; i < n; i++) {
867 int attr = a.getIndex(i);
868
869 switch (attr) {
870 case com.android.internal.R.styleable.TextView_editable:
871 editable = a.getBoolean(attr, editable);
872 break;
873
874 case com.android.internal.R.styleable.TextView_inputMethod:
875 inputMethod = a.getText(attr);
876 break;
877
878 case com.android.internal.R.styleable.TextView_numeric:
879 numeric = a.getInt(attr, numeric);
880 break;
881
882 case com.android.internal.R.styleable.TextView_digits:
883 digits = a.getText(attr);
884 break;
885
886 case com.android.internal.R.styleable.TextView_phoneNumber:
887 phone = a.getBoolean(attr, phone);
888 break;
889
890 case com.android.internal.R.styleable.TextView_autoText:
891 autotext = a.getBoolean(attr, autotext);
892 break;
893
894 case com.android.internal.R.styleable.TextView_capitalize:
895 autocap = a.getInt(attr, autocap);
896 break;
897
898 case com.android.internal.R.styleable.TextView_bufferType:
899 buffertype = a.getInt(attr, buffertype);
900 break;
901
902 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
903 selectallonfocus = a.getBoolean(attr, selectallonfocus);
904 break;
905
906 case com.android.internal.R.styleable.TextView_autoLink:
907 mAutoLinkMask = a.getInt(attr, 0);
908 break;
909
910 case com.android.internal.R.styleable.TextView_linksClickable:
911 mLinksClickable = a.getBoolean(attr, true);
912 break;
913
914 case com.android.internal.R.styleable.TextView_drawableLeft:
915 drawableLeft = a.getDrawable(attr);
916 break;
917
918 case com.android.internal.R.styleable.TextView_drawableTop:
919 drawableTop = a.getDrawable(attr);
920 break;
921
922 case com.android.internal.R.styleable.TextView_drawableRight:
923 drawableRight = a.getDrawable(attr);
924 break;
925
926 case com.android.internal.R.styleable.TextView_drawableBottom:
927 drawableBottom = a.getDrawable(attr);
928 break;
929
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700930 case com.android.internal.R.styleable.TextView_drawableStart:
931 drawableStart = a.getDrawable(attr);
932 break;
933
934 case com.android.internal.R.styleable.TextView_drawableEnd:
935 drawableEnd = a.getDrawable(attr);
936 break;
937
Alan Viveretteb97d6982015-01-07 16:16:20 -0800938 case com.android.internal.R.styleable.TextView_drawableTint:
939 drawableTint = a.getColorStateList(attr);
940 break;
941
942 case com.android.internal.R.styleable.TextView_drawableTintMode:
943 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
944 break;
945
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 case com.android.internal.R.styleable.TextView_drawablePadding:
947 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
948 break;
949
950 case com.android.internal.R.styleable.TextView_maxLines:
951 setMaxLines(a.getInt(attr, -1));
952 break;
953
954 case com.android.internal.R.styleable.TextView_maxHeight:
955 setMaxHeight(a.getDimensionPixelSize(attr, -1));
956 break;
957
958 case com.android.internal.R.styleable.TextView_lines:
959 setLines(a.getInt(attr, -1));
960 break;
961
962 case com.android.internal.R.styleable.TextView_height:
963 setHeight(a.getDimensionPixelSize(attr, -1));
964 break;
965
966 case com.android.internal.R.styleable.TextView_minLines:
967 setMinLines(a.getInt(attr, -1));
968 break;
969
970 case com.android.internal.R.styleable.TextView_minHeight:
971 setMinHeight(a.getDimensionPixelSize(attr, -1));
972 break;
973
974 case com.android.internal.R.styleable.TextView_maxEms:
975 setMaxEms(a.getInt(attr, -1));
976 break;
977
978 case com.android.internal.R.styleable.TextView_maxWidth:
979 setMaxWidth(a.getDimensionPixelSize(attr, -1));
980 break;
981
982 case com.android.internal.R.styleable.TextView_ems:
983 setEms(a.getInt(attr, -1));
984 break;
985
986 case com.android.internal.R.styleable.TextView_width:
987 setWidth(a.getDimensionPixelSize(attr, -1));
988 break;
989
990 case com.android.internal.R.styleable.TextView_minEms:
991 setMinEms(a.getInt(attr, -1));
992 break;
993
994 case com.android.internal.R.styleable.TextView_minWidth:
995 setMinWidth(a.getDimensionPixelSize(attr, -1));
996 break;
997
998 case com.android.internal.R.styleable.TextView_gravity:
999 setGravity(a.getInt(attr, -1));
1000 break;
1001
1002 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -07001003 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 break;
1005
1006 case com.android.internal.R.styleable.TextView_text:
1007 text = a.getText(attr);
1008 break;
1009
1010 case com.android.internal.R.styleable.TextView_scrollHorizontally:
1011 if (a.getBoolean(attr, false)) {
1012 setHorizontallyScrolling(true);
1013 }
1014 break;
1015
1016 case com.android.internal.R.styleable.TextView_singleLine:
1017 singleLine = a.getBoolean(attr, singleLine);
1018 break;
1019
1020 case com.android.internal.R.styleable.TextView_ellipsize:
1021 ellipsize = a.getInt(attr, ellipsize);
1022 break;
1023
1024 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
1025 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
1026 break;
1027
1028 case com.android.internal.R.styleable.TextView_includeFontPadding:
1029 if (!a.getBoolean(attr, true)) {
1030 setIncludeFontPadding(false);
1031 }
1032 break;
1033
1034 case com.android.internal.R.styleable.TextView_cursorVisible:
1035 if (!a.getBoolean(attr, true)) {
1036 setCursorVisible(false);
1037 }
1038 break;
1039
1040 case com.android.internal.R.styleable.TextView_maxLength:
1041 maxlength = a.getInt(attr, -1);
1042 break;
1043
1044 case com.android.internal.R.styleable.TextView_textScaleX:
1045 setTextScaleX(a.getFloat(attr, 1.0f));
1046 break;
1047
1048 case com.android.internal.R.styleable.TextView_freezesText:
1049 mFreezesText = a.getBoolean(attr, false);
1050 break;
1051
1052 case com.android.internal.R.styleable.TextView_shadowColor:
1053 shadowcolor = a.getInt(attr, 0);
1054 break;
1055
1056 case com.android.internal.R.styleable.TextView_shadowDx:
1057 dx = a.getFloat(attr, 0);
1058 break;
1059
1060 case com.android.internal.R.styleable.TextView_shadowDy:
1061 dy = a.getFloat(attr, 0);
1062 break;
1063
1064 case com.android.internal.R.styleable.TextView_shadowRadius:
1065 r = a.getFloat(attr, 0);
1066 break;
1067
1068 case com.android.internal.R.styleable.TextView_enabled:
1069 setEnabled(a.getBoolean(attr, isEnabled()));
1070 break;
1071
1072 case com.android.internal.R.styleable.TextView_textColorHighlight:
1073 textColorHighlight = a.getColor(attr, textColorHighlight);
1074 break;
1075
1076 case com.android.internal.R.styleable.TextView_textColor:
1077 textColor = a.getColorStateList(attr);
1078 break;
1079
1080 case com.android.internal.R.styleable.TextView_textColorHint:
1081 textColorHint = a.getColorStateList(attr);
1082 break;
1083
1084 case com.android.internal.R.styleable.TextView_textColorLink:
1085 textColorLink = a.getColorStateList(attr);
1086 break;
1087
1088 case com.android.internal.R.styleable.TextView_textSize:
1089 textSize = a.getDimensionPixelSize(attr, textSize);
1090 break;
1091
1092 case com.android.internal.R.styleable.TextView_typeface:
1093 typefaceIndex = a.getInt(attr, typefaceIndex);
1094 break;
1095
1096 case com.android.internal.R.styleable.TextView_textStyle:
1097 styleIndex = a.getInt(attr, styleIndex);
1098 break;
1099
Raph Leviend570e892012-05-09 11:45:34 -07001100 case com.android.internal.R.styleable.TextView_fontFamily:
1101 fontFamily = a.getString(attr);
Raph Levien42b30242015-01-29 12:49:19 -08001102 fontFamilyExplicit = true;
Raph Leviend570e892012-05-09 11:45:34 -07001103 break;
1104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 case com.android.internal.R.styleable.TextView_password:
1106 password = a.getBoolean(attr, password);
1107 break;
1108
1109 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1110 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1111 break;
1112
1113 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1114 mSpacingMult = a.getFloat(attr, mSpacingMult);
1115 break;
1116
1117 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001118 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 break;
1120
James Cookf1dad1e2015-02-27 11:00:01 -08001121 case com.android.internal.R.styleable.TextView_allowUndo:
1122 createEditorIfNeeded();
1123 mEditor.mAllowUndo = a.getBoolean(attr, true);
1124 break;
1125
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001126 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001127 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001128 mEditor.createInputContentTypeIfNeeded();
1129 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1130 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131 break;
1132
1133 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001134 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001135 mEditor.createInputContentTypeIfNeeded();
1136 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001137 break;
1138
1139 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001140 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001141 mEditor.createInputContentTypeIfNeeded();
1142 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1143 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 break;
1145
1146 case com.android.internal.R.styleable.TextView_privateImeOptions:
1147 setPrivateImeOptions(a.getString(attr));
1148 break;
1149
1150 case com.android.internal.R.styleable.TextView_editorExtras:
1151 try {
1152 setInputExtras(a.getResourceId(attr, 0));
1153 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001154 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001156 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001157 }
1158 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001159
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001160 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1161 mCursorDrawableRes = a.getResourceId(attr, 0);
1162 break;
1163
Adam Powellb08013c2010-09-16 16:28:11 -07001164 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1165 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1166 break;
1167
1168 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1169 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1170 break;
1171
1172 case com.android.internal.R.styleable.TextView_textSelectHandle:
1173 mTextSelectHandleRes = a.getResourceId(attr, 0);
1174 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001175
Gilles Debunne69340442011-03-31 13:37:51 -07001176 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1177 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1178 break;
1179
Seigo Nonakaa71a2442015-06-19 15:00:43 +09001180 case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout:
1181 mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0);
1182 break;
1183
1184 case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle:
1185 mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0);
1186 break;
1187
Gilles Debunne86b9c782010-11-11 10:43:48 -08001188 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001189 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001190 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001191
Adam Powell7f8f79a2011-07-07 18:35:54 -07001192 case com.android.internal.R.styleable.TextView_textAllCaps:
1193 allCaps = a.getBoolean(attr, false);
1194 break;
Raph Levien53c00772014-04-14 14:11:02 -07001195
1196 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1197 elegant = a.getBoolean(attr, false);
1198 break;
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001199
1200 case com.android.internal.R.styleable.TextView_letterSpacing:
1201 letterSpacing = a.getFloat(attr, 0);
1202 break;
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001203
1204 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1205 fontFeatureSettings = a.getString(attr);
1206 break;
Raph Levien39b4db72015-03-25 13:18:20 -07001207
1208 case com.android.internal.R.styleable.TextView_breakStrategy:
1209 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
Raph Leviene319d5a2015-04-14 23:51:07 -07001210 break;
1211
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07001212 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1213 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1214 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 }
1216 }
1217 a.recycle();
1218
1219 BufferType bufferType = BufferType.EDITABLE;
1220
Gilles Debunned7483bf2010-11-10 10:47:45 -08001221 final int variation =
1222 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1223 final boolean passwordInputType = variation
1224 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1225 final boolean webPasswordInputType = variation
1226 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001227 final boolean numberPasswordInputType = variation
1228 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001229
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001230 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001231 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232
1233 try {
1234 c = Class.forName(inputMethod.toString());
1235 } catch (ClassNotFoundException ex) {
1236 throw new RuntimeException(ex);
1237 }
1238
1239 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001240 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001241 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001242 } catch (InstantiationException ex) {
1243 throw new RuntimeException(ex);
1244 } catch (IllegalAccessException ex) {
1245 throw new RuntimeException(ex);
1246 }
1247 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001248 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001249 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001250 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001251 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001252 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 }
1254 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001255 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001256 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001257 // If no input type was specified, we will default to generic
1258 // text, since we can't tell the IME about the set of digits
1259 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001260 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001261 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262 } else if (inputType != EditorInfo.TYPE_NULL) {
1263 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001264 // If set, the input type overrides what was set using the deprecated singleLine flag.
1265 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001266 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001267 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001268 mEditor.mKeyListener = DialerKeyListener.getInstance();
1269 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001271 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001272 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 (numeric & DECIMAL) != 0);
1274 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1275 if ((numeric & SIGNED) != 0) {
1276 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1277 }
1278 if ((numeric & DECIMAL) != 0) {
1279 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1280 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001281 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 } else if (autotext || autocap != -1) {
1283 TextKeyListener.Capitalize cap;
1284
1285 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001286
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001287 switch (autocap) {
1288 case 1:
1289 cap = TextKeyListener.Capitalize.SENTENCES;
1290 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1291 break;
1292
1293 case 2:
1294 cap = TextKeyListener.Capitalize.WORDS;
1295 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1296 break;
1297
1298 case 3:
1299 cap = TextKeyListener.Capitalize.CHARACTERS;
1300 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1301 break;
1302
1303 default:
1304 cap = TextKeyListener.Capitalize.NONE;
1305 break;
1306 }
1307
Gilles Debunne5fae9962012-05-08 14:53:20 -07001308 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001309 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1310 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001311 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001312 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001313 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001314 mEditor.mKeyListener = null;
1315 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001316 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001317 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001318 // So that selection can be changed using arrow keys and touch is handled.
1319 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001320 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001321 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001322 mEditor.mKeyListener = TextKeyListener.getInstance();
1323 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001325 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001326
1327 switch (buffertype) {
1328 case 0:
1329 bufferType = BufferType.NORMAL;
1330 break;
1331 case 1:
1332 bufferType = BufferType.SPANNABLE;
1333 break;
1334 case 2:
1335 bufferType = BufferType.EDITABLE;
1336 break;
1337 }
1338 }
1339
Gilles Debunne2d373a12012-04-20 15:32:19 -07001340 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1341 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001342
1343 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001344 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001345 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346
1347 if (bufferType == BufferType.NORMAL)
1348 bufferType = BufferType.SPANNABLE;
1349 }
1350
Alan Viveretteb97d6982015-01-07 16:16:20 -08001351 // Set up the tint (if needed) before setting the drawables so that it
1352 // gets applied correctly.
1353 if (drawableTint != null || drawableTintMode != null) {
1354 if (mDrawables == null) {
1355 mDrawables = new Drawables(context);
1356 }
1357 if (drawableTint != null) {
1358 mDrawables.mTintList = drawableTint;
1359 mDrawables.mHasTint = true;
1360 }
1361 if (drawableTintMode != null) {
1362 mDrawables.mTintMode = drawableTintMode;
1363 mDrawables.mHasTintMode = true;
1364 }
1365 }
1366
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001367 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001368 setCompoundDrawablesWithIntrinsicBounds(
1369 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001370 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001371 setCompoundDrawablePadding(drawablePadding);
1372
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001373 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001374 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001375 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001376 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001377
Gilles Debunne60e21862012-01-30 15:04:14 -08001378 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001379 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001380 }
1381
1382 switch (ellipsize) {
1383 case 1:
1384 setEllipsize(TextUtils.TruncateAt.START);
1385 break;
1386 case 2:
1387 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1388 break;
1389 case 3:
1390 setEllipsize(TextUtils.TruncateAt.END);
1391 break;
1392 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001393 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1394 setHorizontalFadingEdgeEnabled(true);
1395 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1396 } else {
1397 setHorizontalFadingEdgeEnabled(false);
1398 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1399 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001400 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1401 break;
1402 }
1403
1404 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1405 setHintTextColor(textColorHint);
1406 setLinkTextColor(textColorLink);
1407 if (textColorHighlight != 0) {
1408 setHighlightColor(textColorHighlight);
1409 }
1410 setRawTextSize(textSize);
Raph Levien53c00772014-04-14 14:11:02 -07001411 setElegantTextHeight(elegant);
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04001412 setLetterSpacing(letterSpacing);
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04001413 setFontFeatureSettings(fontFeatureSettings);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414
Adam Powell7f8f79a2011-07-07 18:35:54 -07001415 if (allCaps) {
1416 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1417 }
1418
Ken Wakasa82d731a2010-12-24 23:42:41 +09001419 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001420 setTransformationMethod(PasswordTransformationMethod.getInstance());
1421 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001422 } else if (mEditor != null &&
1423 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001424 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001425 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001426 }
1427
Raph Levien42b30242015-01-29 12:49:19 -08001428 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1429 fontFamily = null;
1430 }
Raph Leviend570e892012-05-09 11:45:34 -07001431 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001432
1433 if (shadowcolor != 0) {
1434 setShadowLayer(r, dx, dy, shadowcolor);
1435 }
1436
1437 if (maxlength >= 0) {
1438 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1439 } else {
1440 setFilters(NO_FILTERS);
1441 }
1442
1443 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001444 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001445
1446 /*
1447 * Views are not normally focusable unless specified to be.
1448 * However, TextViews that have input or movement methods *are*
1449 * focusable by default.
1450 */
Alan Viverette617feb92013-09-09 18:09:13 -07001451 a = context.obtainStyledAttributes(
1452 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001453
Gilles Debunne60e21862012-01-30 15:04:14 -08001454 boolean focusable = mMovement != null || getKeyListener() != null;
Alan Viverettef32efeb2014-08-14 14:03:21 -07001455 boolean clickable = focusable || isClickable();
1456 boolean longClickable = focusable || isLongClickable();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001457
1458 n = a.getIndexCount();
1459 for (int i = 0; i < n; i++) {
1460 int attr = a.getIndex(i);
1461
1462 switch (attr) {
1463 case com.android.internal.R.styleable.View_focusable:
1464 focusable = a.getBoolean(attr, focusable);
1465 break;
1466
1467 case com.android.internal.R.styleable.View_clickable:
1468 clickable = a.getBoolean(attr, clickable);
1469 break;
1470
1471 case com.android.internal.R.styleable.View_longClickable:
1472 longClickable = a.getBoolean(attr, longClickable);
1473 break;
1474 }
1475 }
1476 a.recycle();
1477
1478 setFocusable(focusable);
1479 setClickable(clickable);
1480 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001481
Gilles Debunned88876a2012-03-16 17:34:04 -07001482 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001483
1484 // If not explicitly specified this view is important for accessibility.
1485 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1486 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001488 }
1489
Raph Leviene319d5a2015-04-14 23:51:07 -07001490 private int[] parseDimensionArray(TypedArray dimens) {
1491 if (dimens == null) {
1492 return null;
1493 }
1494 int[] result = new int[dimens.length()];
1495 for (int i = 0; i < result.length; i++) {
1496 result[i] = dimens.getDimensionPixelSize(i, 0);
1497 }
1498 return result;
1499 }
1500
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001501 /**
1502 * @hide
1503 */
1504 @Override
1505 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1506 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001507 if (resultCode == Activity.RESULT_OK && data != null) {
1508 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1509 if (result != null) {
1510 if (isTextEditable()) {
1511 replaceSelectionWithText(result);
1512 } else {
1513 if (result.length() > 0) {
1514 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1515 .show();
1516 }
1517 }
Andrei Stingaceanu6154b092015-04-20 14:53:47 +01001518 }
Abodunrinwa Toki3b8a5ea2015-10-20 00:19:23 +01001519 } else if (mText instanceof Spannable) {
1520 // Reset the selection.
1521 stopTextActionMode();
1522 Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001523 }
Abodunrinwa Toki3b8a5ea2015-10-20 00:19:23 +01001524
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001525 if (mEditor.hasSelectionController()) {
Clara Bayarri7938cdb2015-06-02 20:03:45 +01001526 mEditor.startSelectionActionMode();
Clara Bayarri5b7665a2015-05-15 16:32:50 +01001527 }
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00001528 }
1529 }
1530
Raph Leviend570e892012-05-09 11:45:34 -07001531 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001532 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001533 if (familyName != null) {
1534 tf = Typeface.create(familyName, styleIndex);
1535 if (tf != null) {
1536 setTypeface(tf);
1537 return;
1538 }
1539 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001540 switch (typefaceIndex) {
1541 case SANS:
1542 tf = Typeface.SANS_SERIF;
1543 break;
1544
1545 case SERIF:
1546 tf = Typeface.SERIF;
1547 break;
1548
1549 case MONOSPACE:
1550 tf = Typeface.MONOSPACE;
1551 break;
1552 }
1553
1554 setTypeface(tf, styleIndex);
1555 }
1556
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001557 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1558 boolean hasRelativeDrawables = (start != null) || (end != null);
1559 if (hasRelativeDrawables) {
1560 Drawables dr = mDrawables;
1561 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001562 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001563 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001564 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001565 final Rect compoundRect = dr.mCompoundRect;
1566 int[] state = getDrawableState();
1567 if (start != null) {
1568 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1569 start.setState(state);
1570 start.copyBounds(compoundRect);
1571 start.setCallback(this);
1572
1573 dr.mDrawableStart = start;
1574 dr.mDrawableSizeStart = compoundRect.width();
1575 dr.mDrawableHeightStart = compoundRect.height();
1576 } else {
1577 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1578 }
1579 if (end != null) {
1580 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1581 end.setState(state);
1582 end.copyBounds(compoundRect);
1583 end.setCallback(this);
1584
1585 dr.mDrawableEnd = end;
1586 dr.mDrawableSizeEnd = compoundRect.width();
1587 dr.mDrawableHeightEnd = compoundRect.height();
1588 } else {
1589 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1590 }
Fabrice Di Meglio4155e2e2013-08-08 16:28:07 -07001591 resetResolvedDrawables();
1592 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08001593 applyCompoundDrawableTint();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001594 }
1595 }
1596
Janos Levai042856c2010-10-15 02:53:58 +03001597 @Override
1598 public void setEnabled(boolean enabled) {
1599 if (enabled == isEnabled()) {
1600 return;
1601 }
1602
1603 if (!enabled) {
1604 // Hide the soft input if the currently active TextView is disabled
1605 InputMethodManager imm = InputMethodManager.peekInstance();
1606 if (imm != null && imm.isActive(this)) {
1607 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1608 }
1609 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001610
Janos Levai042856c2010-10-15 02:53:58 +03001611 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001612
Dianne Hackbornbc823852011-09-18 17:19:50 -07001613 if (enabled) {
1614 // Make sure IME is updated with current editor info.
1615 InputMethodManager imm = InputMethodManager.peekInstance();
1616 if (imm != null) imm.restartInput(this);
1617 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001618
Gilles Debunne33b7de852012-03-12 11:57:48 -07001619 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001620 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001621 mEditor.invalidateTextDisplayList();
1622 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001623
Gilles Debunned88876a2012-03-16 17:34:04 -07001624 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001625 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001626 }
Janos Levai042856c2010-10-15 02:53:58 +03001627 }
1628
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001629 /**
1630 * Sets the typeface and style in which the text should be displayed,
1631 * and turns on the fake bold and italic bits in the Paint if the
1632 * Typeface that you provided does not have all the bits in the
1633 * style that you specified.
1634 *
1635 * @attr ref android.R.styleable#TextView_typeface
1636 * @attr ref android.R.styleable#TextView_textStyle
1637 */
1638 public void setTypeface(Typeface tf, int style) {
1639 if (style > 0) {
1640 if (tf == null) {
1641 tf = Typeface.defaultFromStyle(style);
1642 } else {
1643 tf = Typeface.create(tf, style);
1644 }
1645
1646 setTypeface(tf);
1647 // now compute what (if any) algorithmic styling is needed
1648 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1649 int need = style & ~typefaceStyle;
1650 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1651 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1652 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001653 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001654 mTextPaint.setTextSkewX(0);
1655 setTypeface(tf);
1656 }
1657 }
1658
1659 /**
1660 * Subclasses override this to specify that they have a KeyListener
1661 * by default even if not specifically called for in the XML options.
1662 */
1663 protected boolean getDefaultEditable() {
1664 return false;
1665 }
1666
1667 /**
1668 * Subclasses override this to specify a default movement method.
1669 */
1670 protected MovementMethod getDefaultMovementMethod() {
1671 return null;
1672 }
1673
1674 /**
1675 * Return the text the TextView is displaying. If setText() was called with
1676 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1677 * the return value from this method to Spannable or Editable, respectively.
1678 *
1679 * Note: The content of the return value should not be modified. If you want
1680 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001681 *
1682 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001683 */
1684 @ViewDebug.CapturedViewProperty
1685 public CharSequence getText() {
1686 return mText;
1687 }
1688
1689 /**
1690 * Returns the length, in characters, of the text managed by this TextView
1691 */
1692 public int length() {
1693 return mText.length();
1694 }
1695
1696 /**
1697 * Return the text the TextView is displaying as an Editable object. If
1698 * the text is not editable, null is returned.
1699 *
1700 * @see #getText
1701 */
1702 public Editable getEditableText() {
1703 return (mText instanceof Editable) ? (Editable)mText : null;
1704 }
1705
1706 /**
1707 * @return the height of one standard line in pixels. Note that markup
1708 * within the text can cause individual lines to be taller or shorter
1709 * than this height, and the layout may contain additional first-
1710 * or last-line padding.
1711 */
1712 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001713 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001714 }
1715
1716 /**
1717 * @return the Layout that is currently being used to display the text.
1718 * This can be null if the text or width has recently changes.
1719 */
1720 public final Layout getLayout() {
1721 return mLayout;
1722 }
1723
1724 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001725 * @return the Layout that is currently being used to display the hint text.
1726 * This can be null.
1727 */
1728 final Layout getHintLayout() {
1729 return mHintLayout;
1730 }
1731
1732 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001733 * Retrieve the {@link android.content.UndoManager} that is currently associated
1734 * with this TextView. By default there is no associated UndoManager, so null
1735 * is returned. One can be associated with the TextView through
1736 * {@link #setUndoManager(android.content.UndoManager, String)}
Dianne Hackbornb811e642013-09-04 17:43:56 -07001737 *
1738 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001739 */
1740 public final UndoManager getUndoManager() {
James Cookf59152c2015-02-26 18:03:58 -08001741 // TODO: Consider supporting a global undo manager.
1742 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001743 }
1744
Seigo Nonakaa60160b2015-08-19 12:38:35 -07001745
1746 /**
1747 * @hide
1748 */
1749 @VisibleForTesting
1750 public final Editor getEditorForTesting() {
1751 return mEditor;
1752 }
1753
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001754 /**
1755 * Associate an {@link android.content.UndoManager} with this TextView. Once
1756 * done, all edit operations on the TextView will result in appropriate
1757 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1758 * stack.
1759 *
1760 * @param undoManager The {@link android.content.UndoManager} to associate with
1761 * this TextView, or null to clear any existing association.
1762 * @param tag String tag identifying this particular TextView owner in the
1763 * UndoManager. This is used to keep the correct association with the
1764 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
Dianne Hackbornb811e642013-09-04 17:43:56 -07001765 *
1766 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001767 */
1768 public final void setUndoManager(UndoManager undoManager, String tag) {
James Cookf59152c2015-02-26 18:03:58 -08001769 // TODO: Consider supporting a global undo manager. An implementation will need to:
1770 // * createEditorIfNeeded()
1771 // * Promote to BufferType.EDITABLE if needed.
1772 // * Update the UndoManager and UndoOwner.
1773 // Likewise it will need to be able to restore the default UndoManager.
1774 throw new UnsupportedOperationException("not implemented");
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001775 }
1776
1777 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001778 * @return the current key listener for this TextView.
1779 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001780 *
1781 * @attr ref android.R.styleable#TextView_numeric
1782 * @attr ref android.R.styleable#TextView_digits
1783 * @attr ref android.R.styleable#TextView_phoneNumber
1784 * @attr ref android.R.styleable#TextView_inputMethod
1785 * @attr ref android.R.styleable#TextView_capitalize
1786 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 */
1788 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001789 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001790 }
1791
1792 /**
1793 * Sets the key listener to be used with this TextView. This can be null
1794 * to disallow user input. Note that this method has significant and
1795 * subtle interactions with soft keyboards and other input method:
1796 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1797 * for important details. Calling this method will replace the current
1798 * content type of the text view with the content type returned by the
1799 * key listener.
1800 * <p>
1801 * Be warned that if you want a TextView with a key listener or movement
1802 * method not to be focusable, or if you want a TextView without a
1803 * key listener or movement method to be focusable, you must call
1804 * {@link #setFocusable} again after calling this to get the focusability
1805 * back the way you want it.
1806 *
1807 * @attr ref android.R.styleable#TextView_numeric
1808 * @attr ref android.R.styleable#TextView_digits
1809 * @attr ref android.R.styleable#TextView_phoneNumber
1810 * @attr ref android.R.styleable#TextView_inputMethod
1811 * @attr ref android.R.styleable#TextView_capitalize
1812 * @attr ref android.R.styleable#TextView_autoText
1813 */
1814 public void setKeyListener(KeyListener input) {
1815 setKeyListenerOnly(input);
1816 fixFocusableAndClickableSettings();
1817
1818 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001819 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001820 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001821 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001822 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001823 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001824 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001825 // Change inputType, without affecting transformation.
1826 // No need to applySingleLine since mSingleLine is unchanged.
1827 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001828 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001829 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001830 }
1831
1832 InputMethodManager imm = InputMethodManager.peekInstance();
1833 if (imm != null) imm.restartInput(this);
1834 }
1835
1836 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001837 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001838
Gilles Debunne5fae9962012-05-08 14:53:20 -07001839 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001840 if (mEditor.mKeyListener != input) {
1841 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001842 if (input != null && !(mText instanceof Editable)) {
1843 setText(mText);
1844 }
1845
1846 setFilters((Editable) mText, mFilters);
1847 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001848 }
1849
1850 /**
1851 * @return the movement method being used for this TextView.
1852 * This will frequently be null for non-EditText TextViews.
1853 */
1854 public final MovementMethod getMovementMethod() {
1855 return mMovement;
1856 }
1857
1858 /**
1859 * Sets the movement method (arrow key handler) to be used for
1860 * this TextView. This can be null to disallow using the arrow keys
1861 * to move the cursor or scroll the view.
1862 * <p>
1863 * Be warned that if you want a TextView with a key listener or movement
1864 * method not to be focusable, or if you want a TextView without a
1865 * key listener or movement method to be focusable, you must call
1866 * {@link #setFocusable} again after calling this to get the focusability
1867 * back the way you want it.
1868 */
1869 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001870 if (mMovement != movement) {
1871 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001872
Gilles Debunne60e21862012-01-30 15:04:14 -08001873 if (movement != null && !(mText instanceof Spannable)) {
1874 setText(mText);
1875 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001876
Gilles Debunne60e21862012-01-30 15:04:14 -08001877 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001878
Gilles Debunne2d373a12012-04-20 15:32:19 -07001879 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1880 // mMovement
1881 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001882 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001883 }
1884
1885 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001886 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001887 setFocusable(true);
1888 setClickable(true);
1889 setLongClickable(true);
1890 } else {
1891 setFocusable(false);
1892 setClickable(false);
1893 setLongClickable(false);
1894 }
1895 }
1896
1897 /**
1898 * @return the current transformation method for this TextView.
1899 * This will frequently be null except for single-line and password
1900 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001901 *
1902 * @attr ref android.R.styleable#TextView_password
1903 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001904 */
1905 public final TransformationMethod getTransformationMethod() {
1906 return mTransformation;
1907 }
1908
1909 /**
1910 * Sets the transformation that is applied to the text that this
1911 * TextView is displaying.
1912 *
1913 * @attr ref android.R.styleable#TextView_password
1914 * @attr ref android.R.styleable#TextView_singleLine
1915 */
1916 public final void setTransformationMethod(TransformationMethod method) {
1917 if (method == mTransformation) {
1918 // Avoid the setText() below if the transformation is
1919 // the same.
1920 return;
1921 }
1922 if (mTransformation != null) {
1923 if (mText instanceof Spannable) {
1924 ((Spannable) mText).removeSpan(mTransformation);
1925 }
1926 }
1927
1928 mTransformation = method;
1929
Adam Powell7f8f79a2011-07-07 18:35:54 -07001930 if (method instanceof TransformationMethod2) {
1931 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001932 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001933 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1934 } else {
1935 mAllowTransformationLengthChange = false;
1936 }
1937
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001938 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001939
1940 if (hasPasswordTransformationMethod()) {
Alan Viverette77e9a282013-09-12 17:16:09 -07001941 notifyViewAccessibilityStateChangedIfNeeded(
1942 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001943 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001944 }
1945
1946 /**
1947 * Returns the top padding of the view, plus space for the top
1948 * Drawable if any.
1949 */
1950 public int getCompoundPaddingTop() {
1951 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001952 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001953 return mPaddingTop;
1954 } else {
1955 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1956 }
1957 }
1958
1959 /**
1960 * Returns the bottom padding of the view, plus space for the bottom
1961 * Drawable if any.
1962 */
1963 public int getCompoundPaddingBottom() {
1964 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001965 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001966 return mPaddingBottom;
1967 } else {
1968 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1969 }
1970 }
1971
1972 /**
1973 * Returns the left padding of the view, plus space for the left
1974 * Drawable if any.
1975 */
1976 public int getCompoundPaddingLeft() {
1977 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001978 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001979 return mPaddingLeft;
1980 } else {
1981 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1982 }
1983 }
1984
1985 /**
1986 * Returns the right padding of the view, plus space for the right
1987 * Drawable if any.
1988 */
1989 public int getCompoundPaddingRight() {
1990 final Drawables dr = mDrawables;
Alan Viveretteb97d6982015-01-07 16:16:20 -08001991 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001992 return mPaddingRight;
1993 } else {
1994 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1995 }
1996 }
1997
1998 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001999 * Returns the start padding of the view, plus space for the start
2000 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002001 */
2002 public int getCompoundPaddingStart() {
2003 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07002004 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002005 default:
2006 case LAYOUT_DIRECTION_LTR:
2007 return getCompoundPaddingLeft();
2008 case LAYOUT_DIRECTION_RTL:
2009 return getCompoundPaddingRight();
2010 }
2011 }
2012
2013 /**
2014 * Returns the end padding of the view, plus space for the end
2015 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002016 */
2017 public int getCompoundPaddingEnd() {
2018 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07002019 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002020 default:
2021 case LAYOUT_DIRECTION_LTR:
2022 return getCompoundPaddingRight();
2023 case LAYOUT_DIRECTION_RTL:
2024 return getCompoundPaddingLeft();
2025 }
2026 }
2027
2028 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002029 * Returns the extended top padding of the view, including both the
2030 * top Drawable if any and any extra space to keep more than maxLines
2031 * of text from showing. It is only valid to call this after measuring.
2032 */
2033 public int getExtendedPaddingTop() {
2034 if (mMaxMode != LINES) {
2035 return getCompoundPaddingTop();
2036 }
2037
Raph Levien463cf1a2014-09-02 14:22:23 -07002038 if (mLayout == null) {
2039 assumeLayout();
2040 }
2041
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002042 if (mLayout.getLineCount() <= mMaximum) {
2043 return getCompoundPaddingTop();
2044 }
2045
2046 int top = getCompoundPaddingTop();
2047 int bottom = getCompoundPaddingBottom();
2048 int viewht = getHeight() - top - bottom;
2049 int layoutht = mLayout.getLineTop(mMaximum);
2050
2051 if (layoutht >= viewht) {
2052 return top;
2053 }
2054
2055 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2056 if (gravity == Gravity.TOP) {
2057 return top;
2058 } else if (gravity == Gravity.BOTTOM) {
2059 return top + viewht - layoutht;
2060 } else { // (gravity == Gravity.CENTER_VERTICAL)
2061 return top + (viewht - layoutht) / 2;
2062 }
2063 }
2064
2065 /**
2066 * Returns the extended bottom padding of the view, including both the
2067 * bottom Drawable if any and any extra space to keep more than maxLines
2068 * of text from showing. It is only valid to call this after measuring.
2069 */
2070 public int getExtendedPaddingBottom() {
2071 if (mMaxMode != LINES) {
2072 return getCompoundPaddingBottom();
2073 }
2074
Raph Levien463cf1a2014-09-02 14:22:23 -07002075 if (mLayout == null) {
2076 assumeLayout();
2077 }
2078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002079 if (mLayout.getLineCount() <= mMaximum) {
2080 return getCompoundPaddingBottom();
2081 }
2082
2083 int top = getCompoundPaddingTop();
2084 int bottom = getCompoundPaddingBottom();
2085 int viewht = getHeight() - top - bottom;
2086 int layoutht = mLayout.getLineTop(mMaximum);
2087
2088 if (layoutht >= viewht) {
2089 return bottom;
2090 }
2091
2092 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2093 if (gravity == Gravity.TOP) {
2094 return bottom + viewht - layoutht;
2095 } else if (gravity == Gravity.BOTTOM) {
2096 return bottom;
2097 } else { // (gravity == Gravity.CENTER_VERTICAL)
2098 return bottom + (viewht - layoutht) / 2;
2099 }
2100 }
2101
2102 /**
2103 * Returns the total left padding of the view, including the left
2104 * Drawable if any.
2105 */
2106 public int getTotalPaddingLeft() {
2107 return getCompoundPaddingLeft();
2108 }
2109
2110 /**
2111 * Returns the total right padding of the view, including the right
2112 * Drawable if any.
2113 */
2114 public int getTotalPaddingRight() {
2115 return getCompoundPaddingRight();
2116 }
2117
2118 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002119 * Returns the total start padding of the view, including the start
2120 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002121 */
2122 public int getTotalPaddingStart() {
2123 return getCompoundPaddingStart();
2124 }
2125
2126 /**
2127 * Returns the total end padding of the view, including the end
2128 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002129 */
2130 public int getTotalPaddingEnd() {
2131 return getCompoundPaddingEnd();
2132 }
2133
2134 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002135 * Returns the total top padding of the view, including the top
2136 * Drawable if any, the extra space to keep more than maxLines
2137 * from showing, and the vertical offset for gravity, if any.
2138 */
2139 public int getTotalPaddingTop() {
2140 return getExtendedPaddingTop() + getVerticalOffset(true);
2141 }
2142
2143 /**
2144 * Returns the total bottom padding of the view, including the bottom
2145 * Drawable if any, the extra space to keep more than maxLines
2146 * from showing, and the vertical offset for gravity, if any.
2147 */
2148 public int getTotalPaddingBottom() {
2149 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2150 }
2151
2152 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002153 * Sets the Drawables (if any) to appear to the left of, above, to the
2154 * right of, and below the text. Use {@code null} if you do not want a
2155 * Drawable there. The Drawables must already have had
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002156 * {@link Drawable#setBounds} called.
Alan Viverette97f84ee2014-09-09 16:55:56 -07002157 * <p>
2158 * Calling this method will overwrite any Drawables previously set using
2159 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002160 *
2161 * @attr ref android.R.styleable#TextView_drawableLeft
2162 * @attr ref android.R.styleable#TextView_drawableTop
2163 * @attr ref android.R.styleable#TextView_drawableRight
2164 * @attr ref android.R.styleable#TextView_drawableBottom
2165 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002166 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2167 @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002168 Drawables dr = mDrawables;
2169
Alan Viverette97f84ee2014-09-09 16:55:56 -07002170 // We're switching to absolute, discard relative.
2171 if (dr != null) {
2172 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2173 dr.mDrawableStart = null;
2174 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2175 dr.mDrawableEnd = null;
2176 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2177 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2178 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002179
Alan Viverette97f84ee2014-09-09 16:55:56 -07002180 final boolean drawables = left != null || top != null || right != null || bottom != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002181 if (!drawables) {
2182 // Clearing drawables... can we free the data structure?
2183 if (dr != null) {
Alan Viverette1c9d20b2016-02-03 10:45:55 -05002184 if (!dr.hasMetadata()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002185 mDrawables = null;
2186 } else {
2187 // We need to retain the last set padding, so just clear
2188 // out all of the fields in the existing structure.
Alan Viveretteb97d6982015-01-07 16:16:20 -08002189 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2190 if (dr.mShowing[i] != null) {
2191 dr.mShowing[i].setCallback(null);
2192 }
2193 dr.mShowing[i] = null;
2194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002195 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2196 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2197 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2198 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2199 }
2200 }
2201 } else {
2202 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002203 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002204 }
2205
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002206 mDrawables.mOverride = false;
2207
Alan Viveretteb97d6982015-01-07 16:16:20 -08002208 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2209 dr.mShowing[Drawables.LEFT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002210 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002211 dr.mShowing[Drawables.LEFT] = left;
Romain Guy8e618e52010-03-08 12:18:20 -08002212
Alan Viveretteb97d6982015-01-07 16:16:20 -08002213 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2214 dr.mShowing[Drawables.TOP].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002215 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002216 dr.mShowing[Drawables.TOP] = top;
Romain Guy8e618e52010-03-08 12:18:20 -08002217
Alan Viveretteb97d6982015-01-07 16:16:20 -08002218 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2219 dr.mShowing[Drawables.RIGHT].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002220 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002221 dr.mShowing[Drawables.RIGHT] = right;
Romain Guy8e618e52010-03-08 12:18:20 -08002222
Alan Viveretteb97d6982015-01-07 16:16:20 -08002223 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2224 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Romain Guy48540eb2009-05-19 16:44:57 -07002225 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002226 dr.mShowing[Drawables.BOTTOM] = bottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002227
2228 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07002229 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002230
2231 state = getDrawableState();
2232
2233 if (left != null) {
2234 left.setState(state);
2235 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002236 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002237 dr.mDrawableSizeLeft = compoundRect.width();
2238 dr.mDrawableHeightLeft = compoundRect.height();
2239 } else {
2240 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2241 }
2242
2243 if (right != null) {
2244 right.setState(state);
2245 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002246 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002247 dr.mDrawableSizeRight = compoundRect.width();
2248 dr.mDrawableHeightRight = compoundRect.height();
2249 } else {
2250 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2251 }
2252
2253 if (top != null) {
2254 top.setState(state);
2255 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002256 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002257 dr.mDrawableSizeTop = compoundRect.height();
2258 dr.mDrawableWidthTop = compoundRect.width();
2259 } else {
2260 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2261 }
2262
2263 if (bottom != null) {
2264 bottom.setState(state);
2265 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002266 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002267 dr.mDrawableSizeBottom = compoundRect.height();
2268 dr.mDrawableWidthBottom = compoundRect.width();
2269 } else {
2270 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2271 }
2272 }
2273
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002274 // Save initial left/right drawables
2275 if (dr != null) {
2276 dr.mDrawableLeftInitial = left;
2277 dr.mDrawableRightInitial = right;
2278 }
2279
Fabrice Di Meglio3f5a90b2013-06-24 19:22:25 -07002280 resetResolvedDrawables();
2281 resolveDrawables();
Alan Viveretteb97d6982015-01-07 16:16:20 -08002282 applyCompoundDrawableTint();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002283 invalidate();
2284 requestLayout();
2285 }
2286
2287 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002288 * Sets the Drawables (if any) to appear to the left of, above, to the
2289 * right of, and below the text. Use 0 if you do not want a Drawable there.
2290 * The Drawables' bounds will be set to their intrinsic bounds.
2291 * <p>
2292 * Calling this method will overwrite any Drawables previously set using
2293 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002294 *
2295 * @param left Resource identifier of the left Drawable.
2296 * @param top Resource identifier of the top Drawable.
2297 * @param right Resource identifier of the right Drawable.
2298 * @param bottom Resource identifier of the bottom Drawable.
2299 *
2300 * @attr ref android.R.styleable#TextView_drawableLeft
2301 * @attr ref android.R.styleable#TextView_drawableTop
2302 * @attr ref android.R.styleable#TextView_drawableRight
2303 * @attr ref android.R.styleable#TextView_drawableBottom
2304 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002305 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002306 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2307 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002308 final Context context = getContext();
2309 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2310 top != 0 ? context.getDrawable(top) : null,
2311 right != 0 ? context.getDrawable(right) : null,
2312 bottom != 0 ? context.getDrawable(bottom) : null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002313 }
2314
2315 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002316 * Sets the Drawables (if any) to appear to the left of, above, to the
2317 * right of, and below the text. Use {@code null} if you do not want a
2318 * Drawable there. The Drawables' bounds will be set to their intrinsic
2319 * bounds.
2320 * <p>
2321 * Calling this method will overwrite any Drawables previously set using
2322 * {@link #setCompoundDrawablesRelative} or related methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 *
2324 * @attr ref android.R.styleable#TextView_drawableLeft
2325 * @attr ref android.R.styleable#TextView_drawableTop
2326 * @attr ref android.R.styleable#TextView_drawableRight
2327 * @attr ref android.R.styleable#TextView_drawableBottom
2328 */
Dan Sandler912282e2015-07-28 22:49:30 -04002329 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002330 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2331 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002332
2333 if (left != null) {
2334 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2335 }
2336 if (right != null) {
2337 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2338 }
2339 if (top != null) {
2340 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2341 }
2342 if (bottom != null) {
2343 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2344 }
2345 setCompoundDrawables(left, top, right, bottom);
2346 }
2347
2348 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002349 * Sets the Drawables (if any) to appear to the start of, above, to the end
2350 * of, and below the text. Use {@code null} if you do not want a Drawable
2351 * there. The Drawables must already have had {@link Drawable#setBounds}
2352 * called.
2353 * <p>
2354 * Calling this method will overwrite any Drawables previously set using
2355 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002356 *
2357 * @attr ref android.R.styleable#TextView_drawableStart
2358 * @attr ref android.R.styleable#TextView_drawableTop
2359 * @attr ref android.R.styleable#TextView_drawableEnd
2360 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002361 */
Dan Sandler912282e2015-07-28 22:49:30 -04002362 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002363 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2364 @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002365 Drawables dr = mDrawables;
2366
Alan Viverette97f84ee2014-09-09 16:55:56 -07002367 // We're switching to relative, discard absolute.
2368 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002369 if (dr.mShowing[Drawables.LEFT] != null) {
2370 dr.mShowing[Drawables.LEFT].setCallback(null);
2371 }
2372 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2373 if (dr.mShowing[Drawables.RIGHT] != null) {
2374 dr.mShowing[Drawables.RIGHT].setCallback(null);
2375 }
2376 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
Alan Viverette97f84ee2014-09-09 16:55:56 -07002377 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2378 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2379 }
2380
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002381 final boolean drawables = start != null || top != null
2382 || end != null || bottom != null;
2383
2384 if (!drawables) {
2385 // Clearing drawables... can we free the data structure?
2386 if (dr != null) {
Alan Viverette1c9d20b2016-02-03 10:45:55 -05002387 if (!dr.hasMetadata()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002388 mDrawables = null;
2389 } else {
2390 // We need to retain the last set padding, so just clear
2391 // out all of the fields in the existing structure.
2392 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2393 dr.mDrawableStart = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002394 if (dr.mShowing[Drawables.TOP] != null) {
2395 dr.mShowing[Drawables.TOP].setCallback(null);
2396 }
2397 dr.mShowing[Drawables.TOP] = null;
2398 if (dr.mDrawableEnd != null) {
2399 dr.mDrawableEnd.setCallback(null);
2400 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002401 dr.mDrawableEnd = null;
Alan Viveretteb97d6982015-01-07 16:16:20 -08002402 if (dr.mShowing[Drawables.BOTTOM] != null) {
2403 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2404 }
2405 dr.mShowing[Drawables.BOTTOM] = null;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002406 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2407 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2408 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2409 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2410 }
2411 }
2412 } else {
2413 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002414 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002415 }
2416
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002417 mDrawables.mOverride = true;
2418
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002419 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2420 dr.mDrawableStart.setCallback(null);
2421 }
2422 dr.mDrawableStart = start;
2423
Alan Viveretteb97d6982015-01-07 16:16:20 -08002424 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2425 dr.mShowing[Drawables.TOP].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002426 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002427 dr.mShowing[Drawables.TOP] = top;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002428
2429 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2430 dr.mDrawableEnd.setCallback(null);
2431 }
2432 dr.mDrawableEnd = end;
2433
Alan Viveretteb97d6982015-01-07 16:16:20 -08002434 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2435 dr.mShowing[Drawables.BOTTOM].setCallback(null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002436 }
Alan Viveretteb97d6982015-01-07 16:16:20 -08002437 dr.mShowing[Drawables.BOTTOM] = bottom;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002438
2439 final Rect compoundRect = dr.mCompoundRect;
2440 int[] state;
2441
2442 state = getDrawableState();
2443
2444 if (start != null) {
2445 start.setState(state);
2446 start.copyBounds(compoundRect);
2447 start.setCallback(this);
2448 dr.mDrawableSizeStart = compoundRect.width();
2449 dr.mDrawableHeightStart = compoundRect.height();
2450 } else {
2451 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2452 }
2453
2454 if (end != null) {
2455 end.setState(state);
2456 end.copyBounds(compoundRect);
2457 end.setCallback(this);
2458 dr.mDrawableSizeEnd = compoundRect.width();
2459 dr.mDrawableHeightEnd = compoundRect.height();
2460 } else {
2461 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2462 }
2463
2464 if (top != null) {
2465 top.setState(state);
2466 top.copyBounds(compoundRect);
2467 top.setCallback(this);
2468 dr.mDrawableSizeTop = compoundRect.height();
2469 dr.mDrawableWidthTop = compoundRect.width();
2470 } else {
2471 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2472 }
2473
2474 if (bottom != null) {
2475 bottom.setState(state);
2476 bottom.copyBounds(compoundRect);
2477 bottom.setCallback(this);
2478 dr.mDrawableSizeBottom = compoundRect.height();
2479 dr.mDrawableWidthBottom = compoundRect.width();
2480 } else {
2481 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2482 }
2483 }
2484
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002485 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002486 resolveDrawables();
2487 invalidate();
2488 requestLayout();
2489 }
2490
2491 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002492 * Sets the Drawables (if any) to appear to the start of, above, to the end
2493 * of, and below the text. Use 0 if you do not want a Drawable there. The
2494 * Drawables' bounds will be set to their intrinsic bounds.
2495 * <p>
2496 * Calling this method will overwrite any Drawables previously set using
2497 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002498 *
2499 * @param start Resource identifier of the start Drawable.
2500 * @param top Resource identifier of the top Drawable.
2501 * @param end Resource identifier of the end Drawable.
2502 * @param bottom Resource identifier of the bottom Drawable.
2503 *
2504 * @attr ref android.R.styleable#TextView_drawableStart
2505 * @attr ref android.R.styleable#TextView_drawableTop
2506 * @attr ref android.R.styleable#TextView_drawableEnd
2507 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002508 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002509 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07002510 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2511 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002512 final Context context = getContext();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002513 setCompoundDrawablesRelativeWithIntrinsicBounds(
Alan Viverette8eea3ea2014-02-03 18:40:20 -08002514 start != 0 ? context.getDrawable(start) : null,
2515 top != 0 ? context.getDrawable(top) : null,
2516 end != 0 ? context.getDrawable(end) : null,
2517 bottom != 0 ? context.getDrawable(bottom) : null);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002518 }
2519
2520 /**
Alan Viverette97f84ee2014-09-09 16:55:56 -07002521 * Sets the Drawables (if any) to appear to the start of, above, to the end
2522 * of, and below the text. Use {@code null} if you do not want a Drawable
2523 * there. The Drawables' bounds will be set to their intrinsic bounds.
2524 * <p>
2525 * Calling this method will overwrite any Drawables previously set using
2526 * {@link #setCompoundDrawables} or related methods.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002527 *
2528 * @attr ref android.R.styleable#TextView_drawableStart
2529 * @attr ref android.R.styleable#TextView_drawableTop
2530 * @attr ref android.R.styleable#TextView_drawableEnd
2531 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002532 */
Dan Sandler912282e2015-07-28 22:49:30 -04002533 @android.view.RemotableViewMethod
Alan Viverette97f84ee2014-09-09 16:55:56 -07002534 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2535 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002536
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002537 if (start != null) {
2538 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2539 }
2540 if (end != null) {
2541 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2542 }
2543 if (top != null) {
2544 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2545 }
2546 if (bottom != null) {
2547 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2548 }
2549 setCompoundDrawablesRelative(start, top, end, bottom);
2550 }
2551
2552 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002553 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002554 *
2555 * @attr ref android.R.styleable#TextView_drawableLeft
2556 * @attr ref android.R.styleable#TextView_drawableTop
2557 * @attr ref android.R.styleable#TextView_drawableRight
2558 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002559 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002560 @NonNull
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002561 public Drawable[] getCompoundDrawables() {
2562 final Drawables dr = mDrawables;
2563 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002564 return dr.mShowing.clone();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002565 } else {
2566 return new Drawable[] { null, null, null, null };
2567 }
2568 }
2569
2570 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002571 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002572 *
2573 * @attr ref android.R.styleable#TextView_drawableStart
2574 * @attr ref android.R.styleable#TextView_drawableTop
2575 * @attr ref android.R.styleable#TextView_drawableEnd
2576 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002577 */
Alan Viverette97f84ee2014-09-09 16:55:56 -07002578 @NonNull
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002579 public Drawable[] getCompoundDrawablesRelative() {
2580 final Drawables dr = mDrawables;
2581 if (dr != null) {
2582 return new Drawable[] {
Alan Viveretteb97d6982015-01-07 16:16:20 -08002583 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2584 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002585 };
2586 } else {
2587 return new Drawable[] { null, null, null, null };
2588 }
2589 }
2590
2591 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002592 * Sets the size of the padding between the compound drawables and
2593 * the text.
2594 *
2595 * @attr ref android.R.styleable#TextView_drawablePadding
2596 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002597 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002598 public void setCompoundDrawablePadding(int pad) {
2599 Drawables dr = mDrawables;
2600 if (pad == 0) {
2601 if (dr != null) {
2602 dr.mDrawablePadding = pad;
2603 }
2604 } else {
2605 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002606 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002607 }
2608 dr.mDrawablePadding = pad;
2609 }
2610
2611 invalidate();
2612 requestLayout();
2613 }
2614
2615 /**
2616 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002617 *
2618 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002619 */
2620 public int getCompoundDrawablePadding() {
2621 final Drawables dr = mDrawables;
2622 return dr != null ? dr.mDrawablePadding : 0;
2623 }
2624
Alan Viveretteb97d6982015-01-07 16:16:20 -08002625 /**
2626 * Applies a tint to the compound drawables. Does not modify the
2627 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2628 * <p>
2629 * Subsequent calls to
2630 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2631 * and related methods will automatically mutate the drawables and apply
2632 * the specified tint and tint mode using
2633 * {@link Drawable#setTintList(ColorStateList)}.
2634 *
2635 * @param tint the tint to apply, may be {@code null} to clear tint
2636 *
2637 * @attr ref android.R.styleable#TextView_drawableTint
2638 * @see #getCompoundDrawableTintList()
2639 * @see Drawable#setTintList(ColorStateList)
2640 */
2641 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2642 if (mDrawables == null) {
2643 mDrawables = new Drawables(getContext());
2644 }
2645 mDrawables.mTintList = tint;
2646 mDrawables.mHasTint = true;
2647
2648 applyCompoundDrawableTint();
2649 }
2650
2651 /**
2652 * @return the tint applied to the compound drawables
2653 * @attr ref android.R.styleable#TextView_drawableTint
2654 * @see #setCompoundDrawableTintList(ColorStateList)
2655 */
2656 public ColorStateList getCompoundDrawableTintList() {
2657 return mDrawables != null ? mDrawables.mTintList : null;
2658 }
2659
2660 /**
2661 * Specifies the blending mode used to apply the tint specified by
2662 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2663 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2664 *
2665 * @param tintMode the blending mode used to apply the tint, may be
2666 * {@code null} to clear tint
2667 * @attr ref android.R.styleable#TextView_drawableTintMode
2668 * @see #setCompoundDrawableTintList(ColorStateList)
2669 * @see Drawable#setTintMode(PorterDuff.Mode)
2670 */
2671 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2672 if (mDrawables == null) {
2673 mDrawables = new Drawables(getContext());
2674 }
2675 mDrawables.mTintMode = tintMode;
2676 mDrawables.mHasTintMode = true;
2677
2678 applyCompoundDrawableTint();
2679 }
2680
2681 /**
2682 * Returns the blending mode used to apply the tint to the compound
2683 * drawables, if specified.
2684 *
2685 * @return the blending mode used to apply the tint to the compound
2686 * drawables
2687 * @attr ref android.R.styleable#TextView_drawableTintMode
2688 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2689 */
2690 public PorterDuff.Mode getCompoundDrawableTintMode() {
2691 return mDrawables != null ? mDrawables.mTintMode : null;
2692 }
2693
2694 private void applyCompoundDrawableTint() {
2695 if (mDrawables == null) {
2696 return;
2697 }
2698
2699 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2700 final ColorStateList tintList = mDrawables.mTintList;
2701 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2702 final boolean hasTint = mDrawables.mHasTint;
2703 final boolean hasTintMode = mDrawables.mHasTintMode;
2704 final int[] state = getDrawableState();
2705
2706 for (Drawable dr : mDrawables.mShowing) {
2707 if (dr == null) {
2708 continue;
2709 }
2710
2711 if (dr == mDrawables.mDrawableError) {
2712 // From a developer's perspective, the error drawable isn't
2713 // a compound drawable. Don't apply the generic compound
2714 // drawable tint to it.
2715 continue;
2716 }
2717
2718 dr.mutate();
2719
2720 if (hasTint) {
2721 dr.setTintList(tintList);
2722 }
2723
2724 if (hasTintMode) {
2725 dr.setTintMode(tintMode);
2726 }
2727
2728 // The drawable (or one of its children) may not have been
2729 // stateful before applying the tint, so let's try again.
2730 if (dr.isStateful()) {
2731 dr.setState(state);
2732 }
2733 }
2734 }
2735 }
2736
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002737 @Override
2738 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002739 if (left != mPaddingLeft ||
2740 right != mPaddingRight ||
2741 top != mPaddingTop ||
2742 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002743 nullLayouts();
2744 }
2745
2746 // the super call will requestLayout()
2747 super.setPadding(left, top, right, bottom);
2748 invalidate();
2749 }
2750
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002751 @Override
2752 public void setPaddingRelative(int start, int top, int end, int bottom) {
2753 if (start != getPaddingStart() ||
2754 end != getPaddingEnd() ||
2755 top != mPaddingTop ||
2756 bottom != mPaddingBottom) {
2757 nullLayouts();
2758 }
2759
2760 // the super call will requestLayout()
2761 super.setPaddingRelative(start, top, end, bottom);
2762 invalidate();
2763 }
2764
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002765 /**
2766 * Gets the autolink mask of the text. See {@link
2767 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2768 * possible values.
2769 *
2770 * @attr ref android.R.styleable#TextView_autoLink
2771 */
2772 public final int getAutoLinkMask() {
2773 return mAutoLinkMask;
2774 }
2775
2776 /**
Alan Viverette38082272015-03-16 09:41:30 -07002777 * Sets the text appearance from the specified style resource.
2778 * <p>
2779 * Use a framework-defined {@code TextAppearance} style like
2780 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2781 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2782 * set of attributes that can be used in a custom style.
2783 *
2784 * @param resId the resource identifier of the style to apply
2785 * @attr ref android.R.styleable#TextView_textAppearance
2786 */
2787 @SuppressWarnings("deprecation")
2788 public void setTextAppearance(@StyleRes int resId) {
2789 setTextAppearance(mContext, resId);
2790 }
2791
2792 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002793 * Sets the text color, size, style, hint color, and highlight color
2794 * from the specified TextAppearance resource.
Alan Viverette38082272015-03-16 09:41:30 -07002795 *
2796 * @deprecated Use {@link #setTextAppearance(int)} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002797 */
Alan Viverette38082272015-03-16 09:41:30 -07002798 @Deprecated
2799 public void setTextAppearance(Context context, @StyleRes int resId) {
2800 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002801
Alan Viverette38082272015-03-16 09:41:30 -07002802 final int textColorHighlight = ta.getColor(
2803 R.styleable.TextAppearance_textColorHighlight, 0);
2804 if (textColorHighlight != 0) {
2805 setHighlightColor(textColorHighlight);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002806 }
2807
Alan Viverette38082272015-03-16 09:41:30 -07002808 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2809 if (textColor != null) {
2810 setTextColor(textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002811 }
2812
Alan Viverette38082272015-03-16 09:41:30 -07002813 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2814 if (textSize != 0) {
2815 setRawTextSize(textSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002816 }
2817
Alan Viverette38082272015-03-16 09:41:30 -07002818 final ColorStateList textColorHint = ta.getColorStateList(
2819 R.styleable.TextAppearance_textColorHint);
2820 if (textColorHint != null) {
2821 setHintTextColor(textColorHint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002822 }
2823
Alan Viverette38082272015-03-16 09:41:30 -07002824 final ColorStateList textColorLink = ta.getColorStateList(
2825 R.styleable.TextAppearance_textColorLink);
2826 if (textColorLink != null) {
2827 setLinkTextColor(textColorLink);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002828 }
2829
Alan Viverette38082272015-03-16 09:41:30 -07002830 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2831 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2832 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2833 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002834
Alan Viverette38082272015-03-16 09:41:30 -07002835 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2836 if (shadowColor != 0) {
2837 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2838 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2839 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2840 setShadowLayer(r, dx, dy, shadowColor);
Adam Powellac91df82013-02-14 13:48:47 -08002841 }
2842
Alan Viverette38082272015-03-16 09:41:30 -07002843 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
Adam Powell7f8f79a2011-07-07 18:35:54 -07002844 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2845 }
2846
Alan Viverette38082272015-03-16 09:41:30 -07002847 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2848 setElegantTextHeight(ta.getBoolean(
2849 R.styleable.TextAppearance_elegantTextHeight, false));
Raph Levien53c00772014-04-14 14:11:02 -07002850 }
2851
Alan Viverette38082272015-03-16 09:41:30 -07002852 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2853 setLetterSpacing(ta.getFloat(
2854 R.styleable.TextAppearance_letterSpacing, 0));
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04002855 }
2856
Alan Viverette38082272015-03-16 09:41:30 -07002857 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2858 setFontFeatureSettings(ta.getString(
2859 R.styleable.TextAppearance_fontFeatureSettings));
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04002860 }
2861
Alan Viverette38082272015-03-16 09:41:30 -07002862 ta.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002863 }
2864
2865 /**
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002866 * Get the default primary {@link Locale} of the text in this TextView. This will always be
2867 * the first member of {@link #getTextLocales()}.
2868 * @return the default primary {@link Locale} of the text in this TextView.
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002869 */
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002870 @NonNull
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002871 public Locale getTextLocale() {
2872 return mTextPaint.getTextLocale();
2873 }
2874
2875 /**
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002876 * Get the default {@link LocaleList} of the text in this TextView.
2877 * @return the default {@link LocaleList} of the text in this TextView.
2878 */
2879 @NonNull @Size(min=1)
2880 public LocaleList getTextLocales() {
2881 return mTextPaint.getTextLocales();
2882 }
2883
2884 /**
2885 * Set the default {@link LocaleList} of the text in this TextView to a one-member list
2886 * containing just the given value.
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002887 *
2888 * @param locale the {@link Locale} for drawing text, must not be null.
2889 *
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002890 * @see #setTextLocales
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002891 */
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002892 public void setTextLocale(@NonNull Locale locale) {
2893 mLocalesChanged = true;
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002894 mTextPaint.setTextLocale(locale);
2895 }
2896
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002897 /**
2898 * Set the default {@link LocaleList} of the text in this TextView to the given value.
2899 *
2900 * This value is used to choose appropriate typefaces for ambiguous characters (typically used
2901 * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects
2902 * other aspects of text display, including line breaking.
2903 *
2904 * @param locales the {@link LocaleList} for drawing text, must not be null or empty.
2905 *
2906 * @see Paint#setTextLocales
2907 */
2908 public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
2909 mLocalesChanged = true;
2910 mTextPaint.setTextLocales(locales);
2911 }
2912
Raph Levien2e3aa442015-01-14 16:12:53 -08002913 @Override
2914 protected void onConfigurationChanged(Configuration newConfig) {
2915 super.onConfigurationChanged(newConfig);
Roozbeh Pournadera23748a2015-08-31 14:30:36 -07002916 if (!mLocalesChanged) {
2917 mTextPaint.setTextLocales(LocaleList.getDefault());
Raph Levien2e3aa442015-01-14 16:12:53 -08002918 }
2919 }
2920
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002921 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002922 * @return the size (in pixels) of the default text size in this TextView.
2923 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002924 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002925 public float getTextSize() {
2926 return mTextPaint.getTextSize();
2927 }
2928
2929 /**
Jon Mirandaa25dc4282014-07-01 14:35:07 -07002930 * @return the size (in scaled pixels) of thee default text size in this TextView.
2931 * @hide
2932 */
2933 @ViewDebug.ExportedProperty(category = "text")
2934 public float getScaledTextSize() {
2935 return mTextPaint.getTextSize() / mTextPaint.density;
2936 }
2937
2938 /** @hide */
2939 @ViewDebug.ExportedProperty(category = "text", mapping = {
2940 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2941 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2942 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2943 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2944 })
2945 public int getTypefaceStyle() {
Siva Velusamy94a6d152015-05-05 15:07:00 -07002946 Typeface typeface = mTextPaint.getTypeface();
2947 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
Jon Mirandaa25dc4282014-07-01 14:35:07 -07002948 }
2949
2950 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002951 * Set the default text size to the given value, interpreted as "scaled
2952 * pixel" units. This size is adjusted based on the current density and
2953 * user font size preference.
2954 *
2955 * @param size The scaled pixel size.
2956 *
2957 * @attr ref android.R.styleable#TextView_textSize
2958 */
2959 @android.view.RemotableViewMethod
2960 public void setTextSize(float size) {
2961 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2962 }
2963
2964 /**
2965 * Set the default text size to a given unit and value. See {@link
2966 * TypedValue} for the possible dimension units.
2967 *
2968 * @param unit The desired dimension unit.
2969 * @param size The desired size in the given units.
2970 *
2971 * @attr ref android.R.styleable#TextView_textSize
2972 */
2973 public void setTextSize(int unit, float size) {
2974 Context c = getContext();
2975 Resources r;
2976
2977 if (c == null)
2978 r = Resources.getSystem();
2979 else
2980 r = c.getResources();
2981
2982 setRawTextSize(TypedValue.applyDimension(
Alan Viverettecebc6ba2014-06-13 15:52:13 -07002983 unit, size, r.getDisplayMetrics()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002984 }
2985
2986 private void setRawTextSize(float size) {
2987 if (size != mTextPaint.getTextSize()) {
2988 mTextPaint.setTextSize(size);
2989
2990 if (mLayout != null) {
2991 nullLayouts();
2992 requestLayout();
2993 invalidate();
2994 }
2995 }
2996 }
2997
2998 /**
2999 * @return the extent by which text is currently being stretched
3000 * horizontally. This will usually be 1.
3001 */
3002 public float getTextScaleX() {
3003 return mTextPaint.getTextScaleX();
3004 }
3005
3006 /**
3007 * Sets the extent by which text should be stretched horizontally.
3008 *
3009 * @attr ref android.R.styleable#TextView_textScaleX
3010 */
3011 @android.view.RemotableViewMethod
3012 public void setTextScaleX(float size) {
3013 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07003014 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003015 mTextPaint.setTextScaleX(size);
3016
3017 if (mLayout != null) {
3018 nullLayouts();
3019 requestLayout();
3020 invalidate();
3021 }
3022 }
3023 }
3024
3025 /**
3026 * Sets the typeface and style in which the text should be displayed.
3027 * Note that not all Typeface families actually have bold and italic
3028 * variants, so you may need to use
3029 * {@link #setTypeface(Typeface, int)} to get the appearance
3030 * that you actually want.
3031 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003032 * @see #getTypeface()
3033 *
Raph Leviend570e892012-05-09 11:45:34 -07003034 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003035 * @attr ref android.R.styleable#TextView_typeface
3036 * @attr ref android.R.styleable#TextView_textStyle
3037 */
3038 public void setTypeface(Typeface tf) {
3039 if (mTextPaint.getTypeface() != tf) {
3040 mTextPaint.setTypeface(tf);
3041
3042 if (mLayout != null) {
3043 nullLayouts();
3044 requestLayout();
3045 invalidate();
3046 }
3047 }
3048 }
3049
3050 /**
3051 * @return the current typeface and style in which the text is being
3052 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07003053 *
3054 * @see #setTypeface(Typeface)
3055 *
Raph Leviend570e892012-05-09 11:45:34 -07003056 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07003057 * @attr ref android.R.styleable#TextView_typeface
3058 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003059 */
3060 public Typeface getTypeface() {
3061 return mTextPaint.getTypeface();
3062 }
3063
3064 /**
Raph Levien53c00772014-04-14 14:11:02 -07003065 * Set the TextView's elegant height metrics flag. This setting selects font
3066 * variants that have not been compacted to fit Latin-based vertical
3067 * metrics, and also increases top and bottom bounds to provide more space.
3068 *
3069 * @param elegant set the paint's elegant metrics flag.
Raph Leviene1c4a0d2014-06-05 15:26:59 -07003070 *
3071 * @attr ref android.R.styleable#TextView_elegantTextHeight
Raph Levien53c00772014-04-14 14:11:02 -07003072 */
3073 public void setElegantTextHeight(boolean elegant) {
3074 mTextPaint.setElegantTextHeight(elegant);
3075 }
3076
3077 /**
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003078 * @return the extent by which text is currently being letter-spaced.
3079 * This will normally be 0.
3080 *
3081 * @see #setLetterSpacing(float)
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003082 * @see Paint#setLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003083 */
3084 public float getLetterSpacing() {
3085 return mTextPaint.getLetterSpacing();
3086 }
3087
3088 /**
3089 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3090 * for slight expansion will be around 0.05. Negative values tighten text.
3091 *
3092 * @see #getLetterSpacing()
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003093 * @see Paint#getLetterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003094 *
3095 * @attr ref android.R.styleable#TextView_letterSpacing
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003096 */
3097 @android.view.RemotableViewMethod
3098 public void setLetterSpacing(float letterSpacing) {
3099 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3100 mTextPaint.setLetterSpacing(letterSpacing);
3101
3102 if (mLayout != null) {
3103 nullLayouts();
3104 requestLayout();
3105 invalidate();
3106 }
3107 }
3108 }
3109
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003110 /**
3111 * @return the currently set font feature settings. Default is null.
3112 *
3113 * @see #setFontFeatureSettings(String)
3114 * @see Paint#setFontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003115 */
Raph Leviene272a262014-08-07 16:07:51 -07003116 @Nullable
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003117 public String getFontFeatureSettings() {
3118 return mTextPaint.getFontFeatureSettings();
3119 }
3120
3121 /**
Raph Levien39b4db72015-03-25 13:18:20 -07003122 * Sets the break strategy for breaking paragraphs into lines. The default value for
3123 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3124 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3125 * text "dancing" when being edited.
3126 *
3127 * @attr ref android.R.styleable#TextView_breakStrategy
3128 * @see #getBreakStrategy()
3129 */
3130 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3131 mBreakStrategy = breakStrategy;
3132 if (mLayout != null) {
3133 nullLayouts();
3134 requestLayout();
3135 invalidate();
3136 }
3137 }
3138
3139 /**
3140 * @return the currently set break strategy.
3141 *
3142 * @attr ref android.R.styleable#TextView_breakStrategy
3143 * @see #setBreakStrategy(int)
3144 */
3145 @Layout.BreakStrategy
3146 public int getBreakStrategy() {
3147 return mBreakStrategy;
3148 }
3149
3150 /**
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07003151 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3152 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3153 *
3154 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3155 * @see #getHyphenationFrequency()
3156 */
3157 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3158 mHyphenationFrequency = hyphenationFrequency;
3159 if (mLayout != null) {
3160 nullLayouts();
3161 requestLayout();
3162 invalidate();
3163 }
3164 }
3165
3166 /**
3167 * @return the currently set hyphenation frequency.
3168 *
3169 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3170 * @see #setHyphenationFrequency(int)
3171 */
3172 @Layout.HyphenationFrequency
3173 public int getHyphenationFrequency() {
3174 return mHyphenationFrequency;
3175 }
3176
3177 /**
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003178 * Sets font feature settings. The format is the same as the CSS
3179 * font-feature-settings attribute:
3180 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3181 *
Raph Leviene272a262014-08-07 16:07:51 -07003182 * @param fontFeatureSettings font feature settings represented as CSS compatible string
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003183 * @see #getFontFeatureSettings()
3184 * @see Paint#getFontFeatureSettings
3185 *
3186 * @attr ref android.R.styleable#TextView_fontFeatureSettings
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003187 */
3188 @android.view.RemotableViewMethod
Raph Leviene272a262014-08-07 16:07:51 -07003189 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
Behdad Esfahbode9ad3932014-07-30 19:46:53 -04003190 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3191 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3192
3193 if (mLayout != null) {
3194 nullLayouts();
3195 requestLayout();
3196 invalidate();
3197 }
3198 }
3199 }
3200
Behdad Esfahbodfa80f742014-07-17 19:10:39 -04003201
3202 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003203 * Sets the text color for all the states (normal, selected,
3204 * focused) to be this color.
3205 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003206 * @see #setTextColor(ColorStateList)
3207 * @see #getTextColors()
3208 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003209 * @attr ref android.R.styleable#TextView_textColor
3210 */
3211 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003212 public void setTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003213 mTextColor = ColorStateList.valueOf(color);
3214 updateTextColors();
3215 }
3216
3217 /**
3218 * Sets the text color.
3219 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003220 * @see #setTextColor(int)
3221 * @see #getTextColors()
3222 * @see #setHintTextColor(ColorStateList)
3223 * @see #setLinkTextColor(ColorStateList)
3224 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003225 * @attr ref android.R.styleable#TextView_textColor
3226 */
3227 public void setTextColor(ColorStateList colors) {
3228 if (colors == null) {
3229 throw new NullPointerException();
3230 }
3231
3232 mTextColor = colors;
3233 updateTextColors();
3234 }
3235
3236 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003237 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003238 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003239 * @see #setTextColor(ColorStateList)
3240 * @see #setTextColor(int)
3241 *
3242 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003243 */
3244 public final ColorStateList getTextColors() {
3245 return mTextColor;
3246 }
3247
3248 /**
3249 * <p>Return the current color selected for normal text.</p>
3250 *
3251 * @return Returns the current text color.
3252 */
Tor Norbye80756e32015-03-02 09:39:27 -08003253 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003254 public final int getCurrentTextColor() {
3255 return mCurTextColor;
3256 }
3257
3258 /**
3259 * Sets the color used to display the selection highlight.
3260 *
3261 * @attr ref android.R.styleable#TextView_textColorHighlight
3262 */
3263 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003264 public void setHighlightColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003265 if (mHighlightColor != color) {
3266 mHighlightColor = color;
3267 invalidate();
3268 }
3269 }
3270
3271 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003272 * @return the color used to display the selection highlight
3273 *
3274 * @see #setHighlightColor(int)
3275 *
3276 * @attr ref android.R.styleable#TextView_textColorHighlight
3277 */
Tor Norbye80756e32015-03-02 09:39:27 -08003278 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003279 public int getHighlightColor() {
3280 return mHighlightColor;
3281 }
3282
3283 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003284 * Sets whether the soft input method will be made visible when this
3285 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003286 */
3287 @android.view.RemotableViewMethod
3288 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003289 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003290 mEditor.mShowSoftInputOnFocus = show;
3291 }
3292
3293 /**
3294 * Returns whether the soft input method will be made visible when this
3295 * TextView gets focused. The default is true.
Gilles Debunne3473b2b2012-04-20 16:21:10 -07003296 */
3297 public final boolean getShowSoftInputOnFocus() {
3298 // When there is no Editor, return default true value
3299 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3300 }
3301
3302 /**
Chris Craik5faf85b2014-08-20 17:16:05 -07003303 * Gives the text a shadow of the specified blur radius and color, the specified
3304 * distance from its drawn position.
3305 * <p>
3306 * The text shadow produced does not interact with the properties on view
3307 * that are responsible for real time shadows,
3308 * {@link View#getElevation() elevation} and
3309 * {@link View#getTranslationZ() translationZ}.
3310 *
3311 * @see Paint#setShadowLayer(float, float, float, int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003312 *
3313 * @attr ref android.R.styleable#TextView_shadowColor
3314 * @attr ref android.R.styleable#TextView_shadowDx
3315 * @attr ref android.R.styleable#TextView_shadowDy
3316 * @attr ref android.R.styleable#TextView_shadowRadius
3317 */
3318 public void setShadowLayer(float radius, float dx, float dy, int color) {
3319 mTextPaint.setShadowLayer(radius, dx, dy, color);
3320
3321 mShadowRadius = radius;
3322 mShadowDx = dx;
3323 mShadowDy = dy;
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003324 mShadowColor = color;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003325
Gilles Debunne33b7de852012-03-12 11:57:48 -07003326 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07003327 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003328 invalidate();
3329 }
3330
3331 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003332 * Gets the radius of the shadow layer.
3333 *
3334 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3335 *
3336 * @see #setShadowLayer(float, float, float, int)
3337 *
3338 * @attr ref android.R.styleable#TextView_shadowRadius
3339 */
3340 public float getShadowRadius() {
3341 return mShadowRadius;
3342 }
3343
3344 /**
3345 * @return the horizontal offset of the shadow layer
3346 *
3347 * @see #setShadowLayer(float, float, float, int)
3348 *
3349 * @attr ref android.R.styleable#TextView_shadowDx
3350 */
3351 public float getShadowDx() {
3352 return mShadowDx;
3353 }
3354
3355 /**
3356 * @return the vertical offset of the shadow layer
3357 *
3358 * @see #setShadowLayer(float, float, float, int)
3359 *
3360 * @attr ref android.R.styleable#TextView_shadowDy
3361 */
3362 public float getShadowDy() {
3363 return mShadowDy;
3364 }
3365
3366 /**
3367 * @return the color of the shadow layer
3368 *
3369 * @see #setShadowLayer(float, float, float, int)
3370 *
3371 * @attr ref android.R.styleable#TextView_shadowColor
3372 */
Tor Norbye80756e32015-03-02 09:39:27 -08003373 @ColorInt
Gilles Debunnef03acef2012-04-30 19:26:19 -07003374 public int getShadowColor() {
Derek Sollenbergerc29a0a42014-03-31 13:52:39 -04003375 return mShadowColor;
Gilles Debunnef03acef2012-04-30 19:26:19 -07003376 }
3377
3378 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003379 * @return the base paint used for the text. Please use this only to
3380 * consult the Paint's properties and not to change them.
3381 */
3382 public TextPaint getPaint() {
3383 return mTextPaint;
3384 }
3385
3386 /**
3387 * Sets the autolink mask of the text. See {@link
3388 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3389 * possible values.
3390 *
3391 * @attr ref android.R.styleable#TextView_autoLink
3392 */
3393 @android.view.RemotableViewMethod
3394 public final void setAutoLinkMask(int mask) {
3395 mAutoLinkMask = mask;
3396 }
3397
3398 /**
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08003399 * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
3400 * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
3401 * following is true:
3402 * <ul>
3403 * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
3404 * {@link #setText} or {@link #append}.
3405 * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
3406 * </ul>
3407 *
3408 * <p>This function does not have an immediate effect, movement method will be set only after a
3409 * call to {@link #setText} or {@link #append}. The default is true.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003410 *
3411 * @attr ref android.R.styleable#TextView_linksClickable
3412 */
3413 @android.view.RemotableViewMethod
3414 public final void setLinksClickable(boolean whether) {
3415 mLinksClickable = whether;
3416 }
3417
3418 /**
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08003419 * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
3420 * after {@link #setText} or {@link #append} is called.
3421 *
3422 * See {@link #setLinksClickable} for details.
3423 *
3424 * <p>The default is true.</p>
3425 *
3426 * @see #setLinksClickable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003427 *
3428 * @attr ref android.R.styleable#TextView_linksClickable
3429 */
3430 public final boolean getLinksClickable() {
3431 return mLinksClickable;
3432 }
3433
3434 /**
3435 * Returns the list of URLSpans attached to the text
3436 * (by {@link Linkify} or otherwise) if any. You can call
3437 * {@link URLSpan#getURL} on them to find where they link to
3438 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3439 * to find the region of the text they are attached to.
3440 */
3441 public URLSpan[] getUrls() {
3442 if (mText instanceof Spanned) {
3443 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3444 } else {
3445 return new URLSpan[0];
3446 }
3447 }
3448
3449 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003450 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3451 * TextView.
3452 *
3453 * @see #setHintTextColor(ColorStateList)
3454 * @see #getHintTextColors()
3455 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003456 *
3457 * @attr ref android.R.styleable#TextView_textColorHint
3458 */
3459 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003460 public final void setHintTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003461 mHintTextColor = ColorStateList.valueOf(color);
3462 updateTextColors();
3463 }
3464
3465 /**
3466 * Sets the color of the hint text.
3467 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003468 * @see #getHintTextColors()
3469 * @see #setHintTextColor(int)
3470 * @see #setTextColor(ColorStateList)
3471 * @see #setLinkTextColor(ColorStateList)
3472 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 * @attr ref android.R.styleable#TextView_textColorHint
3474 */
3475 public final void setHintTextColor(ColorStateList colors) {
3476 mHintTextColor = colors;
3477 updateTextColors();
3478 }
3479
3480 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003481 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003482 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003483 * @see #setHintTextColor(ColorStateList)
3484 * @see #setHintTextColor(int)
3485 * @see #setTextColor(ColorStateList)
3486 * @see #setLinkTextColor(ColorStateList)
3487 *
3488 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003489 */
3490 public final ColorStateList getHintTextColors() {
3491 return mHintTextColor;
3492 }
3493
3494 /**
3495 * <p>Return the current color selected to paint the hint text.</p>
3496 *
3497 * @return Returns the current hint text color.
3498 */
Tor Norbye80756e32015-03-02 09:39:27 -08003499 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003500 public final int getCurrentHintTextColor() {
3501 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3502 }
3503
3504 /**
3505 * Sets the color of links in the text.
3506 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003507 * @see #setLinkTextColor(ColorStateList)
3508 * @see #getLinkTextColors()
3509 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003510 * @attr ref android.R.styleable#TextView_textColorLink
3511 */
3512 @android.view.RemotableViewMethod
Tor Norbye80756e32015-03-02 09:39:27 -08003513 public final void setLinkTextColor(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003514 mLinkTextColor = ColorStateList.valueOf(color);
3515 updateTextColors();
3516 }
3517
3518 /**
3519 * Sets the color of links in the text.
3520 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003521 * @see #setLinkTextColor(int)
3522 * @see #getLinkTextColors()
3523 * @see #setTextColor(ColorStateList)
3524 * @see #setHintTextColor(ColorStateList)
3525 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003526 * @attr ref android.R.styleable#TextView_textColorLink
3527 */
3528 public final void setLinkTextColor(ColorStateList colors) {
3529 mLinkTextColor = colors;
3530 updateTextColors();
3531 }
3532
3533 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003534 * @return the list of colors used to paint the links in the text, for the different states of
3535 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003536 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003537 * @see #setLinkTextColor(ColorStateList)
3538 * @see #setLinkTextColor(int)
3539 *
3540 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003541 */
3542 public final ColorStateList getLinkTextColors() {
3543 return mLinkTextColor;
3544 }
3545
3546 /**
3547 * Sets the horizontal alignment of the text and the
3548 * vertical gravity that will be used when there is extra space
3549 * in the TextView beyond what is required for the text itself.
3550 *
3551 * @see android.view.Gravity
3552 * @attr ref android.R.styleable#TextView_gravity
3553 */
3554 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003555 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07003556 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003557 }
3558 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3559 gravity |= Gravity.TOP;
3560 }
3561
3562 boolean newLayout = false;
3563
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07003564 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3565 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003566 newLayout = true;
3567 }
3568
3569 if (gravity != mGravity) {
3570 invalidate();
3571 }
3572
3573 mGravity = gravity;
3574
3575 if (mLayout != null && newLayout) {
3576 // XXX this is heavy-handed because no actual content changes.
3577 int want = mLayout.getWidth();
3578 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3579
3580 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3581 mRight - mLeft - getCompoundPaddingLeft() -
3582 getCompoundPaddingRight(), true);
3583 }
3584 }
3585
3586 /**
3587 * Returns the horizontal and vertical alignment of this TextView.
3588 *
3589 * @see android.view.Gravity
3590 * @attr ref android.R.styleable#TextView_gravity
3591 */
3592 public int getGravity() {
3593 return mGravity;
3594 }
3595
3596 /**
3597 * @return the flags on the Paint being used to display the text.
3598 * @see Paint#getFlags
3599 */
3600 public int getPaintFlags() {
3601 return mTextPaint.getFlags();
3602 }
3603
3604 /**
3605 * Sets flags on the Paint being used to display the text and
3606 * reflows the text if they are different from the old flags.
3607 * @see Paint#setFlags
3608 */
3609 @android.view.RemotableViewMethod
3610 public void setPaintFlags(int flags) {
3611 if (mTextPaint.getFlags() != flags) {
3612 mTextPaint.setFlags(flags);
3613
3614 if (mLayout != null) {
3615 nullLayouts();
3616 requestLayout();
3617 invalidate();
3618 }
3619 }
3620 }
3621
3622 /**
3623 * Sets whether the text should be allowed to be wider than the
3624 * View is. If false, it will be wrapped to the width of the View.
3625 *
3626 * @attr ref android.R.styleable#TextView_scrollHorizontally
3627 */
3628 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003629 if (mHorizontallyScrolling != whether) {
3630 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003631
Gilles Debunne22378292011-08-12 10:38:52 -07003632 if (mLayout != null) {
3633 nullLayouts();
3634 requestLayout();
3635 invalidate();
3636 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003637 }
3638 }
3639
3640 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003641 * Returns whether the text is allowed to be wider than the View is.
3642 * If false, the text will be wrapped to the width of the View.
3643 *
3644 * @attr ref android.R.styleable#TextView_scrollHorizontally
3645 * @hide
3646 */
3647 public boolean getHorizontallyScrolling() {
3648 return mHorizontallyScrolling;
3649 }
3650
3651 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003652 * Makes the TextView at least this many lines tall.
3653 *
3654 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3655 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003656 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003657 * @see #getMinLines()
3658 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003659 * @attr ref android.R.styleable#TextView_minLines
3660 */
3661 @android.view.RemotableViewMethod
3662 public void setMinLines(int minlines) {
3663 mMinimum = minlines;
3664 mMinMode = LINES;
3665
3666 requestLayout();
3667 invalidate();
3668 }
3669
3670 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003671 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3672 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3673 *
3674 * @see #setMinLines(int)
3675 *
3676 * @attr ref android.R.styleable#TextView_minLines
3677 */
3678 public int getMinLines() {
3679 return mMinMode == LINES ? mMinimum : -1;
3680 }
3681
3682 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003683 * Makes the TextView at least this many pixels tall.
3684 *
3685 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003686 *
3687 * @attr ref android.R.styleable#TextView_minHeight
3688 */
3689 @android.view.RemotableViewMethod
3690 public void setMinHeight(int minHeight) {
3691 mMinimum = minHeight;
3692 mMinMode = PIXELS;
3693
3694 requestLayout();
3695 invalidate();
3696 }
3697
3698 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003699 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3700 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3701 *
3702 * @see #setMinHeight(int)
3703 *
3704 * @attr ref android.R.styleable#TextView_minHeight
3705 */
3706 public int getMinHeight() {
3707 return mMinMode == PIXELS ? mMinimum : -1;
3708 }
3709
3710 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003711 * Makes the TextView at most this many lines tall.
3712 *
3713 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003714 *
3715 * @attr ref android.R.styleable#TextView_maxLines
3716 */
3717 @android.view.RemotableViewMethod
3718 public void setMaxLines(int maxlines) {
3719 mMaximum = maxlines;
3720 mMaxMode = LINES;
3721
3722 requestLayout();
3723 invalidate();
3724 }
3725
3726 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003727 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3728 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3729 *
3730 * @see #setMaxLines(int)
3731 *
3732 * @attr ref android.R.styleable#TextView_maxLines
3733 */
3734 public int getMaxLines() {
3735 return mMaxMode == LINES ? mMaximum : -1;
3736 }
3737
3738 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003739 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3740 * {@link #setMaxLines(int)} method.
3741 *
3742 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003743 *
3744 * @attr ref android.R.styleable#TextView_maxHeight
3745 */
3746 @android.view.RemotableViewMethod
3747 public void setMaxHeight(int maxHeight) {
3748 mMaximum = maxHeight;
3749 mMaxMode = PIXELS;
3750
3751 requestLayout();
3752 invalidate();
3753 }
3754
3755 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003756 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3757 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3758 *
3759 * @see #setMaxHeight(int)
3760 *
3761 * @attr ref android.R.styleable#TextView_maxHeight
3762 */
3763 public int getMaxHeight() {
3764 return mMaxMode == PIXELS ? mMaximum : -1;
3765 }
3766
3767 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003768 * Makes the TextView exactly this many lines tall.
3769 *
3770 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3771 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003772 *
3773 * @attr ref android.R.styleable#TextView_lines
3774 */
3775 @android.view.RemotableViewMethod
3776 public void setLines(int lines) {
3777 mMaximum = mMinimum = lines;
3778 mMaxMode = mMinMode = LINES;
3779
3780 requestLayout();
3781 invalidate();
3782 }
3783
3784 /**
3785 * Makes the TextView exactly this many pixels tall.
3786 * You could do the same thing by specifying this number in the
3787 * LayoutParams.
3788 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003789 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3790 * height setting.
3791 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 * @attr ref android.R.styleable#TextView_height
3793 */
3794 @android.view.RemotableViewMethod
3795 public void setHeight(int pixels) {
3796 mMaximum = mMinimum = pixels;
3797 mMaxMode = mMinMode = PIXELS;
3798
3799 requestLayout();
3800 invalidate();
3801 }
3802
3803 /**
3804 * Makes the TextView at least this many ems wide
3805 *
3806 * @attr ref android.R.styleable#TextView_minEms
3807 */
3808 @android.view.RemotableViewMethod
3809 public void setMinEms(int minems) {
3810 mMinWidth = minems;
3811 mMinWidthMode = EMS;
3812
3813 requestLayout();
3814 invalidate();
3815 }
3816
3817 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003818 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3819 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3820 *
3821 * @see #setMinEms(int)
3822 * @see #setEms(int)
3823 *
3824 * @attr ref android.R.styleable#TextView_minEms
3825 */
3826 public int getMinEms() {
3827 return mMinWidthMode == EMS ? mMinWidth : -1;
3828 }
3829
3830 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003831 * Makes the TextView at least this many pixels wide
3832 *
3833 * @attr ref android.R.styleable#TextView_minWidth
3834 */
3835 @android.view.RemotableViewMethod
3836 public void setMinWidth(int minpixels) {
3837 mMinWidth = minpixels;
3838 mMinWidthMode = PIXELS;
3839
3840 requestLayout();
3841 invalidate();
3842 }
3843
3844 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003845 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3846 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3847 *
3848 * @see #setMinWidth(int)
3849 * @see #setWidth(int)
3850 *
3851 * @attr ref android.R.styleable#TextView_minWidth
3852 */
3853 public int getMinWidth() {
3854 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3855 }
3856
3857 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003858 * Makes the TextView at most this many ems wide
3859 *
3860 * @attr ref android.R.styleable#TextView_maxEms
3861 */
3862 @android.view.RemotableViewMethod
3863 public void setMaxEms(int maxems) {
3864 mMaxWidth = maxems;
3865 mMaxWidthMode = EMS;
3866
3867 requestLayout();
3868 invalidate();
3869 }
3870
3871 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003872 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3873 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3874 *
3875 * @see #setMaxEms(int)
3876 * @see #setEms(int)
3877 *
3878 * @attr ref android.R.styleable#TextView_maxEms
3879 */
3880 public int getMaxEms() {
3881 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3882 }
3883
3884 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003885 * Makes the TextView at most this many pixels wide
3886 *
3887 * @attr ref android.R.styleable#TextView_maxWidth
3888 */
3889 @android.view.RemotableViewMethod
3890 public void setMaxWidth(int maxpixels) {
3891 mMaxWidth = maxpixels;
3892 mMaxWidthMode = PIXELS;
3893
3894 requestLayout();
3895 invalidate();
3896 }
3897
3898 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003899 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3900 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3901 *
3902 * @see #setMaxWidth(int)
3903 * @see #setWidth(int)
3904 *
3905 * @attr ref android.R.styleable#TextView_maxWidth
3906 */
3907 public int getMaxWidth() {
3908 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3909 }
3910
3911 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003912 * Makes the TextView exactly this many ems wide
3913 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003914 * @see #setMaxEms(int)
3915 * @see #setMinEms(int)
3916 * @see #getMinEms()
3917 * @see #getMaxEms()
3918 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003919 * @attr ref android.R.styleable#TextView_ems
3920 */
3921 @android.view.RemotableViewMethod
3922 public void setEms(int ems) {
3923 mMaxWidth = mMinWidth = ems;
3924 mMaxWidthMode = mMinWidthMode = EMS;
3925
3926 requestLayout();
3927 invalidate();
3928 }
3929
3930 /**
3931 * Makes the TextView exactly this many pixels wide.
3932 * You could do the same thing by specifying this number in the
3933 * LayoutParams.
3934 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003935 * @see #setMaxWidth(int)
3936 * @see #setMinWidth(int)
3937 * @see #getMinWidth()
3938 * @see #getMaxWidth()
3939 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003940 * @attr ref android.R.styleable#TextView_width
3941 */
3942 @android.view.RemotableViewMethod
3943 public void setWidth(int pixels) {
3944 mMaxWidth = mMinWidth = pixels;
3945 mMaxWidthMode = mMinWidthMode = PIXELS;
3946
3947 requestLayout();
3948 invalidate();
3949 }
3950
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003951 /**
3952 * Sets line spacing for this TextView. Each line will have its height
3953 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3954 *
3955 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3956 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3957 */
3958 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003959 if (mSpacingAdd != add || mSpacingMult != mult) {
3960 mSpacingAdd = add;
3961 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003962
Gilles Debunne22378292011-08-12 10:38:52 -07003963 if (mLayout != null) {
3964 nullLayouts();
3965 requestLayout();
3966 invalidate();
3967 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003968 }
3969 }
3970
3971 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003972 * Gets the line spacing multiplier
3973 *
3974 * @return the value by which each line's height is multiplied to get its actual height.
3975 *
3976 * @see #setLineSpacing(float, float)
3977 * @see #getLineSpacingExtra()
3978 *
3979 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3980 */
3981 public float getLineSpacingMultiplier() {
3982 return mSpacingMult;
3983 }
3984
3985 /**
3986 * Gets the line spacing extra space
3987 *
3988 * @return the extra space that is added to the height of each lines of this TextView.
3989 *
3990 * @see #setLineSpacing(float, float)
3991 * @see #getLineSpacingMultiplier()
3992 *
3993 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3994 */
3995 public float getLineSpacingExtra() {
3996 return mSpacingAdd;
3997 }
3998
3999 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004000 * Convenience method: Append the specified text to the TextView's
4001 * display buffer, upgrading it to BufferType.EDITABLE if it was
4002 * not already editable.
4003 */
4004 public final void append(CharSequence text) {
4005 append(text, 0, text.length());
4006 }
4007
4008 /**
4009 * Convenience method: Append the specified text slice to the TextView's
4010 * display buffer, upgrading it to BufferType.EDITABLE if it was
4011 * not already editable.
4012 */
4013 public void append(CharSequence text, int start, int end) {
4014 if (!(mText instanceof Editable)) {
4015 setText(mText, BufferType.EDITABLE);
4016 }
4017
4018 ((Editable) mText).append(text, start, end);
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004019
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004020 boolean hasClickableSpans = false;
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004021 if (mAutoLinkMask != 0) {
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004022 hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
4023 } else if (mLinksClickable && text instanceof Spanned) {
4024 ClickableSpan[] clickableSpans =
4025 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4026 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4027 }
4028
4029 // Do not change the movement method for text that supports text selection as it
4030 // would prevent an arbitrary cursor displacement.
4031 if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
4032 setMovementMethod(LinkMovementMethod.getInstance());
Siyamed Sinira0db63f2015-11-10 15:34:29 -08004033 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004034 }
4035
4036 private void updateTextColors() {
4037 boolean inval = false;
4038 int color = mTextColor.getColorForState(getDrawableState(), 0);
4039 if (color != mCurTextColor) {
4040 mCurTextColor = color;
4041 inval = true;
4042 }
4043 if (mLinkTextColor != null) {
4044 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
4045 if (color != mTextPaint.linkColor) {
4046 mTextPaint.linkColor = color;
4047 inval = true;
4048 }
4049 }
4050 if (mHintTextColor != null) {
4051 color = mHintTextColor.getColorForState(getDrawableState(), 0);
Raph Levienc1bf2852014-11-12 12:41:24 -08004052 if (color != mCurHintTextColor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004053 mCurHintTextColor = color;
Raph Levienc1bf2852014-11-12 12:41:24 -08004054 if (mText.length() == 0) {
4055 inval = true;
4056 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004057 }
4058 }
4059 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07004060 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07004061 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004062 invalidate();
4063 }
4064 }
4065
4066 @Override
4067 protected void drawableStateChanged() {
4068 super.drawableStateChanged();
Alan Viverettead0020f2015-09-04 10:10:42 -04004069
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004070 if (mTextColor != null && mTextColor.isStateful()
4071 || (mHintTextColor != null && mHintTextColor.isStateful())
4072 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
4073 updateTextColors();
4074 }
4075
Alan Viveretteb97d6982015-01-07 16:16:20 -08004076 if (mDrawables != null) {
4077 final int[] state = getDrawableState();
4078 for (Drawable dr : mDrawables.mShowing) {
Alan Viverettead0020f2015-09-04 10:10:42 -04004079 if (dr != null && dr.isStateful() && dr.setState(state)) {
4080 invalidateDrawable(dr);
Alan Viveretteb97d6982015-01-07 16:16:20 -08004081 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004082 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004083 }
4084 }
4085
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004086 @Override
Alan Viverette8de14942014-06-18 18:05:15 -07004087 public void drawableHotspotChanged(float x, float y) {
4088 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004089
Alan Viveretteb97d6982015-01-07 16:16:20 -08004090 if (mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08004091 for (Drawable dr : mDrawables.mShowing) {
Alan Viverettead0020f2015-09-04 10:10:42 -04004092 if (dr != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08004093 dr.setHotspot(x, y);
4094 }
Alan Viverettecebc6ba2014-06-13 15:52:13 -07004095 }
4096 }
4097 }
4098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004099 @Override
4100 public Parcelable onSaveInstanceState() {
4101 Parcelable superState = super.onSaveInstanceState();
4102
4103 // Save state if we are forced to
Siyamed Sinir02771192016-02-05 16:08:59 -08004104 final boolean freezesText = getFreezesText();
4105 boolean hasSelection = false;
4106 int start = -1;
4107 int end = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004108
4109 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004110 start = getSelectionStart();
4111 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004112 if (start >= 0 || end >= 0) {
4113 // Or save state if there is a selection
Siyamed Sinir02771192016-02-05 16:08:59 -08004114 hasSelection = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004115 }
4116 }
4117
Siyamed Sinir02771192016-02-05 16:08:59 -08004118 if (freezesText || hasSelection) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004119 SavedState ss = new SavedState(superState);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004120
Siyamed Sinir02771192016-02-05 16:08:59 -08004121 if (freezesText) {
4122 if (mText instanceof Spanned) {
4123 final Spannable sp = new SpannableStringBuilder(mText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004124
Siyamed Sinir02771192016-02-05 16:08:59 -08004125 if (mEditor != null) {
4126 removeMisspelledSpans(sp);
4127 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4128 }
4129
4130 ss.text = sp;
4131 } else {
4132 ss.text = mText.toString();
Gilles Debunne60e21862012-01-30 15:04:14 -08004133 }
Siyamed Sinir02771192016-02-05 16:08:59 -08004134 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07004135
Siyamed Sinir02771192016-02-05 16:08:59 -08004136 if (hasSelection) {
4137 // XXX Should also save the current scroll position!
4138 ss.selStart = start;
4139 ss.selEnd = end;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004140 }
4141
4142 if (isFocused() && start >= 0 && end >= 0) {
4143 ss.frozenWithFocus = true;
4144 }
4145
Gilles Debunne60e21862012-01-30 15:04:14 -08004146 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08004147
James Cookf59152c2015-02-26 18:03:58 -08004148 if (mEditor != null) {
4149 ss.editorState = mEditor.saveInstanceState();
4150 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004151 return ss;
4152 }
4153
4154 return superState;
4155 }
4156
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07004157 void removeMisspelledSpans(Spannable spannable) {
4158 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4159 SuggestionSpan.class);
4160 for (int i = 0; i < suggestionSpans.length; i++) {
4161 int flags = suggestionSpans[i].getFlags();
4162 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4163 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4164 spannable.removeSpan(suggestionSpans[i]);
4165 }
4166 }
4167 }
4168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004169 @Override
4170 public void onRestoreInstanceState(Parcelable state) {
4171 if (!(state instanceof SavedState)) {
4172 super.onRestoreInstanceState(state);
4173 return;
4174 }
4175
4176 SavedState ss = (SavedState)state;
4177 super.onRestoreInstanceState(ss.getSuperState());
4178
4179 // XXX restore buffer type too, as well as lots of other stuff
4180 if (ss.text != null) {
4181 setText(ss.text);
4182 }
4183
4184 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4185 if (mText instanceof Spannable) {
4186 int len = mText.length();
4187
4188 if (ss.selStart > len || ss.selEnd > len) {
4189 String restored = "";
4190
4191 if (ss.text != null) {
4192 restored = "(restored) ";
4193 }
4194
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004195 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004196 "/" + ss.selEnd + " out of range for " + restored +
4197 "text " + mText);
4198 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08004199 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004200
4201 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004202 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004203 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004204 }
4205 }
4206 }
4207 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08004208
4209 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07004210 final CharSequence error = ss.error;
4211 // Display the error later, after the first layout pass
4212 post(new Runnable() {
4213 public void run() {
Alexander Toresson545a8bb2013-10-14 14:59:31 +02004214 if (mEditor == null || !mEditor.mErrorWasChanged) {
4215 setError(error);
4216 }
Romain Guy9bc9fa12009-07-21 16:57:29 -07004217 }
4218 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08004219 }
James Cookf59152c2015-02-26 18:03:58 -08004220
4221 if (ss.editorState != null) {
4222 createEditorIfNeeded();
4223 mEditor.restoreInstanceState(ss.editorState);
4224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004225 }
4226
4227 /**
4228 * Control whether this text view saves its entire text contents when
4229 * freezing to an icicle, in addition to dynamic state such as cursor
4230 * position. By default this is false, not saving the text. Set to true
4231 * if the text in the text view is not being saved somewhere else in
4232 * persistent storage (such as in a content provider) so that if the
Siyamed Sinir02771192016-02-05 16:08:59 -08004233 * view is later thawed the user will not lose their data. For
4234 * {@link android.widget.EditText} it is always enabled, regardless of
4235 * the value of the attribute.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004236 *
4237 * @param freezesText Controls whether a frozen icicle should include the
4238 * entire text data: true to include it, false to not.
4239 *
4240 * @attr ref android.R.styleable#TextView_freezesText
4241 */
4242 @android.view.RemotableViewMethod
4243 public void setFreezesText(boolean freezesText) {
4244 mFreezesText = freezesText;
4245 }
4246
4247 /**
4248 * Return whether this text view is including its entire text contents
Siyamed Sinir02771192016-02-05 16:08:59 -08004249 * in frozen icicles. For {@link android.widget.EditText} it always returns true.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004250 *
4251 * @return Returns true if text is included, false if it isn't.
4252 *
4253 * @see #setFreezesText
4254 */
4255 public boolean getFreezesText() {
4256 return mFreezesText;
4257 }
4258
4259 ///////////////////////////////////////////////////////////////////////////
4260
4261 /**
4262 * Sets the Factory used to create new Editables.
4263 */
4264 public final void setEditableFactory(Editable.Factory factory) {
4265 mEditableFactory = factory;
4266 setText(mText);
4267 }
4268
4269 /**
4270 * Sets the Factory used to create new Spannables.
4271 */
4272 public final void setSpannableFactory(Spannable.Factory factory) {
4273 mSpannableFactory = factory;
4274 setText(mText);
4275 }
4276
4277 /**
4278 * Sets the string value of the TextView. TextView <em>does not</em> accept
4279 * HTML-like formatting, which you can do with text strings in XML resource files.
4280 * To style your strings, attach android.text.style.* objects to a
4281 * {@link android.text.SpannableString SpannableString}, or see the
4282 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07004283 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004284 * formatted text in the XML resource file.
4285 *
4286 * @attr ref android.R.styleable#TextView_text
4287 */
4288 @android.view.RemotableViewMethod
4289 public final void setText(CharSequence text) {
4290 setText(text, mBufferType);
4291 }
4292
4293 /**
4294 * Like {@link #setText(CharSequence)},
4295 * except that the cursor position (if any) is retained in the new text.
4296 *
4297 * @param text The new text to place in the text view.
4298 *
4299 * @see #setText(CharSequence)
4300 */
4301 @android.view.RemotableViewMethod
4302 public final void setTextKeepState(CharSequence text) {
4303 setTextKeepState(text, mBufferType);
4304 }
4305
4306 /**
4307 * Sets the text that this TextView is to display (see
4308 * {@link #setText(CharSequence)}) and also sets whether it is stored
4309 * in a styleable/spannable buffer and whether it is editable.
4310 *
4311 * @attr ref android.R.styleable#TextView_text
4312 * @attr ref android.R.styleable#TextView_bufferType
4313 */
4314 public void setText(CharSequence text, BufferType type) {
4315 setText(text, type, true, 0);
4316
4317 if (mCharWrapper != null) {
4318 mCharWrapper.mChars = null;
4319 }
4320 }
4321
4322 private void setText(CharSequence text, BufferType type,
4323 boolean notifyBefore, int oldlen) {
4324 if (text == null) {
4325 text = "";
4326 }
4327
Luca Zanoline0760452011-09-08 12:03:37 +01004328 // If suggestions are not enabled, remove the suggestion spans from the text
4329 if (!isSuggestionsEnabled()) {
4330 text = removeSuggestionSpans(text);
4331 }
4332
Romain Guy939151f2009-04-08 14:22:40 -07004333 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004335 if (text instanceof Spanned &&
4336 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07004337 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4338 setHorizontalFadingEdgeEnabled(true);
4339 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4340 } else {
4341 setHorizontalFadingEdgeEnabled(false);
4342 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4343 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004344 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4345 }
4346
4347 int n = mFilters.length;
4348 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08004349 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004350 if (out != null) {
4351 text = out;
4352 }
4353 }
4354
4355 if (notifyBefore) {
4356 if (mText != null) {
4357 oldlen = mText.length();
4358 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4359 } else {
4360 sendBeforeTextChanged("", 0, 0, text.length());
4361 }
4362 }
4363
4364 boolean needEditableForNotification = false;
4365
4366 if (mListeners != null && mListeners.size() != 0) {
4367 needEditableForNotification = true;
4368 }
4369
Gilles Debunne2d373a12012-04-20 15:32:19 -07004370 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4371 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004372 createEditorIfNeeded();
James Cook48e0fac2015-02-25 15:44:51 -08004373 mEditor.forgetUndoRedo();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004374 Editable t = mEditableFactory.newEditable(text);
4375 text = t;
4376 setFilters(t, mFilters);
4377 InputMethodManager imm = InputMethodManager.peekInstance();
4378 if (imm != null) imm.restartInput(this);
4379 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4380 text = mSpannableFactory.newSpannable(text);
4381 } else if (!(text instanceof CharWrapper)) {
4382 text = TextUtils.stringOrSpannedString(text);
4383 }
4384
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004385 boolean hasClickableSpans = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004386 if (mAutoLinkMask != 0) {
4387 Spannable s2;
4388
4389 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4390 s2 = (Spannable) text;
4391 } else {
4392 s2 = mSpannableFactory.newSpannable(text);
4393 }
4394
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004395 hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
4396 if (hasClickableSpans) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004397 text = s2;
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004398 }
4399 } else if (mLinksClickable && text instanceof Spanned) {
4400 ClickableSpan[] clickableSpans =
4401 ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
4402 hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
4403 if (hasClickableSpans && !(text instanceof Spannable)) {
4404 text = mSpannableFactory.newSpannable(text);
4405 }
4406 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004407
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004408 if (hasClickableSpans) {
4409 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4410 /*
4411 * We must go ahead and set the text before changing the
4412 * movement method, because setMovementMethod() may call
4413 * setText() again to try to upgrade the buffer type.
4414 */
4415 mText = text;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004416
Siyamed Sinir4ba0aa52015-11-16 15:39:34 -08004417 // Do not change the movement method for text that supports text selection as it
4418 // would prevent an arbitrary cursor displacement.
4419 if (mLinksClickable && !textCanBeSelected()) {
4420 setMovementMethod(LinkMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004421 }
4422 }
4423
4424 mBufferType = type;
4425 mText = text;
4426
Adam Powell7f8f79a2011-07-07 18:35:54 -07004427 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004428 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07004429 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004430 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07004431 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004432
4433 final int textLength = text.length();
4434
Adam Powell7f8f79a2011-07-07 18:35:54 -07004435 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004436 Spannable sp = (Spannable) text;
4437
Gilles Debunnec62589c2012-04-12 14:50:23 -07004438 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004439 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4440 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07004441 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004442 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07004443 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004444
Gilles Debunnec62589c2012-04-12 14:50:23 -07004445 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004446
4447 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08004448 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004449
Gilles Debunnec62589c2012-04-12 14:50:23 -07004450 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004451
4452 if (mTransformation != null) {
4453 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004454 }
4455
4456 if (mMovement != null) {
4457 mMovement.initialize(this, (Spannable) text);
4458
4459 /*
4460 * Initializing the movement method will have set the
4461 * selection, so reset mSelectionMoved to keep that from
4462 * interfering with the normal on-focus selection-setting.
4463 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07004464 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004465 }
4466 }
4467
4468 if (mLayout != null) {
4469 checkForRelayout();
4470 }
4471
4472 sendOnTextChanged(text, 0, oldlen, textLength);
4473 onTextChanged(text, 0, oldlen, textLength);
4474
Alan Viverette77e9a282013-09-12 17:16:09 -07004475 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4476
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004477 if (needEditableForNotification) {
4478 sendAfterTextChanged((Editable) text);
4479 }
Gilles Debunne05336272010-07-09 20:13:45 -07004480
Gilles Debunnebaaace52010-10-01 15:47:13 -07004481 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07004482 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004483 }
4484
4485 /**
4486 * Sets the TextView to display the specified slice of the specified
4487 * char array. You must promise that you will not change the contents
4488 * of the array except for right before another call to setText(),
4489 * since the TextView has no way to know that the text
4490 * has changed and that it needs to invalidate and re-layout.
4491 */
4492 public final void setText(char[] text, int start, int len) {
4493 int oldlen = 0;
4494
4495 if (start < 0 || len < 0 || start + len > text.length) {
4496 throw new IndexOutOfBoundsException(start + ", " + len);
4497 }
4498
4499 /*
4500 * We must do the before-notification here ourselves because if
4501 * the old text is a CharWrapper we destroy it before calling
4502 * into the normal path.
4503 */
4504 if (mText != null) {
4505 oldlen = mText.length();
4506 sendBeforeTextChanged(mText, 0, oldlen, len);
4507 } else {
4508 sendBeforeTextChanged("", 0, 0, len);
4509 }
4510
4511 if (mCharWrapper == null) {
4512 mCharWrapper = new CharWrapper(text, start, len);
4513 } else {
4514 mCharWrapper.set(text, start, len);
4515 }
4516
4517 setText(mCharWrapper, mBufferType, false, oldlen);
4518 }
4519
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004520 /**
4521 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4522 * except that the cursor position (if any) is retained in the new text.
4523 *
4524 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4525 */
4526 public final void setTextKeepState(CharSequence text, BufferType type) {
4527 int start = getSelectionStart();
4528 int end = getSelectionEnd();
4529 int len = text.length();
4530
4531 setText(text, type);
4532
4533 if (start >= 0 || end >= 0) {
4534 if (mText instanceof Spannable) {
4535 Selection.setSelection((Spannable) mText,
4536 Math.max(0, Math.min(start, len)),
4537 Math.max(0, Math.min(end, len)));
4538 }
4539 }
4540 }
4541
4542 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004543 public final void setText(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004544 setText(getContext().getResources().getText(resid));
4545 }
4546
Tor Norbye7b9c9122013-05-30 16:48:33 -07004547 public final void setText(@StringRes int resid, BufferType type) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004548 setText(getContext().getResources().getText(resid), type);
4549 }
4550
4551 /**
4552 * Sets the text to be displayed when the text of the TextView is empty.
4553 * Null means to use the normal empty text. The hint does not currently
4554 * participate in determining the size of the view.
4555 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004556 * @attr ref android.R.styleable#TextView_hint
4557 */
4558 @android.view.RemotableViewMethod
4559 public final void setHint(CharSequence hint) {
4560 mHint = TextUtils.stringOrSpannedString(hint);
4561
4562 if (mLayout != null) {
4563 checkForRelayout();
4564 }
4565
Romain Guy4dc4f732009-06-19 15:16:40 -07004566 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004567 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07004568 }
Gilles Debunne626c3162012-02-14 15:46:41 -08004569
Gilles Debunne33b7de852012-03-12 11:57:48 -07004570 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08004571 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004572 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08004573 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004574 }
4575
4576 /**
4577 * Sets the text to be displayed when the text of the TextView is empty,
4578 * from a resource.
4579 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004580 * @attr ref android.R.styleable#TextView_hint
4581 */
4582 @android.view.RemotableViewMethod
Tor Norbye7b9c9122013-05-30 16:48:33 -07004583 public final void setHint(@StringRes int resid) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004584 setHint(getContext().getResources().getText(resid));
4585 }
4586
4587 /**
4588 * Returns the hint that is displayed when the text of the TextView
4589 * is empty.
4590 *
4591 * @attr ref android.R.styleable#TextView_hint
4592 */
4593 @ViewDebug.CapturedViewProperty
4594 public CharSequence getHint() {
4595 return mHint;
4596 }
4597
Gilles Debunned88876a2012-03-16 17:34:04 -07004598 boolean isSingleLine() {
4599 return mSingleLine;
4600 }
4601
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004602 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004603 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4604 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4605 }
4606
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004607 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07004608 * Removes the suggestion spans.
4609 */
4610 CharSequence removeSuggestionSpans(CharSequence text) {
4611 if (text instanceof Spanned) {
4612 Spannable spannable;
4613 if (text instanceof Spannable) {
4614 spannable = (Spannable) text;
4615 } else {
4616 spannable = new SpannableString(text);
4617 text = spannable;
4618 }
4619
4620 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4621 for (int i = 0; i < spans.length; i++) {
4622 spannable.removeSpan(spans[i]);
4623 }
4624 }
4625 return text;
4626 }
4627
4628 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004629 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4630 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4631 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4632 * then a soft keyboard will not be displayed for this text view.
4633 *
4634 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4635 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4636 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004637 *
4638 * @see #getInputType()
4639 * @see #setRawInputType(int)
4640 * @see android.text.InputType
4641 * @attr ref android.R.styleable#TextView_inputType
4642 */
4643 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004644 final boolean wasPassword = isPasswordInputType(getInputType());
4645 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004646 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004647 final boolean isPassword = isPasswordInputType(type);
4648 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004649 boolean forceUpdate = false;
4650 if (isPassword) {
4651 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07004652 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004653 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07004654 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4655 forceUpdate = true;
4656 }
Raph Leviend570e892012-05-09 11:45:34 -07004657 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004658 } else if (wasPassword || wasVisiblePassword) {
4659 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004660 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004661 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4662 forceUpdate = true;
4663 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004664 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004665
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004666 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004668 // We need to update the single line mode if it has changed or we
4669 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004670 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004671 // Change single line mode, but only change the transformation if
4672 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004673 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004674 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004675
Luca Zanoline0760452011-09-08 12:03:37 +01004676 if (!isSuggestionsEnabled()) {
4677 mText = removeSuggestionSpans(mText);
4678 }
4679
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004680 InputMethodManager imm = InputMethodManager.peekInstance();
4681 if (imm != null) imm.restartInput(this);
4682 }
4683
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004684 /**
4685 * It would be better to rely on the input type for everything. A password inputType should have
4686 * a password transformation. We should hence use isPasswordInputType instead of this method.
4687 *
4688 * We should:
4689 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4690 * would install the correct transformation).
4691 * - Refuse the installation of a non-password transformation in setTransformation if the input
4692 * type is password.
4693 *
4694 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4695 * is useful since it matches what the user can see (obfuscated text or not).
4696 *
4697 * @return true if the current transformation method is of the password type.
4698 */
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01004699 boolean hasPasswordTransformationMethod() {
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004700 return mTransformation instanceof PasswordTransformationMethod;
4701 }
4702
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004703 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004704 final int variation =
4705 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004706 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004707 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4708 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004709 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4710 || variation
4711 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004712 }
4713
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004714 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004715 final int variation =
4716 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004717 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004718 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004719 }
4720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004721 /**
4722 * Directly change the content type integer of the text view, without
4723 * modifying any other state.
4724 * @see #setInputType(int)
4725 * @see android.text.InputType
4726 * @attr ref android.R.styleable#TextView_inputType
4727 */
4728 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004729 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004730 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004731 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004732 }
4733
4734 private void setInputType(int type, boolean direct) {
4735 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4736 KeyListener input;
4737 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004738 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004739 TextKeyListener.Capitalize cap;
4740 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4741 cap = TextKeyListener.Capitalize.CHARACTERS;
4742 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4743 cap = TextKeyListener.Capitalize.WORDS;
4744 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4745 cap = TextKeyListener.Capitalize.SENTENCES;
4746 } else {
4747 cap = TextKeyListener.Capitalize.NONE;
4748 }
4749 input = TextKeyListener.getInstance(autotext, cap);
4750 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4751 input = DigitsKeyListener.getInstance(
4752 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4753 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4754 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4755 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4756 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4757 input = DateKeyListener.getInstance();
4758 break;
4759 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4760 input = TimeKeyListener.getInstance();
4761 break;
4762 default:
4763 input = DateTimeKeyListener.getInstance();
4764 break;
4765 }
4766 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4767 input = DialerKeyListener.getInstance();
4768 } else {
4769 input = TextKeyListener.getInstance();
4770 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004771 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004772 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004773 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004774 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004775 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004776 setKeyListenerOnly(input);
4777 }
4778 }
4779
4780 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004781 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004782 *
4783 * @see #setInputType(int)
4784 * @see android.text.InputType
4785 */
4786 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004787 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004788 }
4789
4790 /**
4791 * Change the editor type integer associated with the text view, which
4792 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4793 * has focus.
4794 * @see #getImeOptions
4795 * @see android.view.inputmethod.EditorInfo
4796 * @attr ref android.R.styleable#TextView_imeOptions
4797 */
4798 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004799 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004800 mEditor.createInputContentTypeIfNeeded();
4801 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004802 }
4803
4804 /**
4805 * Get the type of the IME editor.
4806 *
4807 * @see #setImeOptions(int)
4808 * @see android.view.inputmethod.EditorInfo
4809 */
4810 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004811 return mEditor != null && mEditor.mInputContentType != null
4812 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004813 }
4814
4815 /**
4816 * Change the custom IME action associated with the text view, which
4817 * will be reported to an IME with {@link EditorInfo#actionLabel}
4818 * and {@link EditorInfo#actionId} when it has focus.
4819 * @see #getImeActionLabel
4820 * @see #getImeActionId
4821 * @see android.view.inputmethod.EditorInfo
4822 * @attr ref android.R.styleable#TextView_imeActionLabel
4823 * @attr ref android.R.styleable#TextView_imeActionId
4824 */
4825 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004826 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004827 mEditor.createInputContentTypeIfNeeded();
4828 mEditor.mInputContentType.imeActionLabel = label;
4829 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004830 }
4831
4832 /**
4833 * Get the IME action label previous set with {@link #setImeActionLabel}.
4834 *
4835 * @see #setImeActionLabel
4836 * @see android.view.inputmethod.EditorInfo
4837 */
4838 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004839 return mEditor != null && mEditor.mInputContentType != null
4840 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004841 }
4842
4843 /**
4844 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4845 *
4846 * @see #setImeActionLabel
4847 * @see android.view.inputmethod.EditorInfo
4848 */
4849 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004850 return mEditor != null && mEditor.mInputContentType != null
4851 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004852 }
4853
4854 /**
4855 * Set a special listener to be called when an action is performed
4856 * on the text view. This will be called when the enter key is pressed,
4857 * or when an action supplied to the IME is selected by the user. Setting
4858 * this means that the normal hard key event will not insert a newline
4859 * into the text view, even if it is multi-line; holding down the ALT
4860 * modifier will, however, allow the user to insert a newline character.
4861 */
4862 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004863 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004864 mEditor.createInputContentTypeIfNeeded();
4865 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004866 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004868 /**
4869 * Called when an attached input method calls
4870 * {@link InputConnection#performEditorAction(int)
4871 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004872 * for this text view. The default implementation will call your action
4873 * listener supplied to {@link #setOnEditorActionListener}, or perform
4874 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004875 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4876 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004877 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004878 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004879 * <p>For backwards compatibility, if no IME options have been set and the
4880 * text view would not normally advance focus on enter, then
4881 * the NEXT and DONE actions received here will be turned into an enter
4882 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004883 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004884 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004885 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004886 * @see #setOnEditorActionListener
4887 */
4888 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004889 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004890 if (ict != null) {
4891 if (ict.onEditorActionListener != null) {
4892 if (ict.onEditorActionListener.onEditorAction(this,
4893 actionCode, null)) {
4894 return;
4895 }
4896 }
Gilles Debunne64794482011-11-30 15:45:28 -08004897
The Android Open Source Project4df24232009-03-05 14:34:35 -08004898 // This is the handling for some default action.
4899 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004900 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004901 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004902 // app may be expecting.
4903 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004904 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004905 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004906 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004907 throw new IllegalStateException("focus search returned a view " +
4908 "that wasn't able to take focus!");
4909 }
4910 }
4911 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004912
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004913 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004914 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004915 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004916 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004917 throw new IllegalStateException("focus search returned a view " +
4918 "that wasn't able to take focus!");
4919 }
4920 }
4921 return;
4922
The Android Open Source Project4df24232009-03-05 14:34:35 -08004923 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4924 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004925 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004926 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004927 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004928 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004929 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004930 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004931
Jeff Browna175a5b2012-02-15 19:18:31 -08004932 ViewRootImpl viewRootImpl = getViewRootImpl();
4933 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004934 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004935 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004936 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004937 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4938 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004939 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004940 | KeyEvent.FLAG_EDITOR_ACTION));
4941 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004942 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004943 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4944 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004945 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004946 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004947 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004948 }
Gilles Debunne64794482011-11-30 15:45:28 -08004949
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004950 /**
4951 * Set the private content type of the text, which is the
4952 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4953 * field that will be filled in when creating an input connection.
4954 *
4955 * @see #getPrivateImeOptions()
4956 * @see EditorInfo#privateImeOptions
4957 * @attr ref android.R.styleable#TextView_privateImeOptions
4958 */
4959 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004960 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004961 mEditor.createInputContentTypeIfNeeded();
4962 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004963 }
4964
4965 /**
4966 * Get the private type of the content.
4967 *
4968 * @see #setPrivateImeOptions(String)
4969 * @see EditorInfo#privateImeOptions
4970 */
4971 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004972 return mEditor != null && mEditor.mInputContentType != null
4973 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004974 }
4975
4976 /**
4977 * Set the extra input data of the text, which is the
4978 * {@link EditorInfo#extras TextBoxAttribute.extras}
4979 * Bundle that will be filled in when creating an input connection. The
4980 * given integer is the resource ID of an XML resource holding an
4981 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4982 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004983 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004984 * @see EditorInfo#extras
4985 * @attr ref android.R.styleable#TextView_editorExtras
4986 */
Tor Norbye7b9c9122013-05-30 16:48:33 -07004987 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004988 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004989 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004990 mEditor.createInputContentTypeIfNeeded();
4991 mEditor.mInputContentType.extras = new Bundle();
4992 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004993 }
4994
4995 /**
4996 * Retrieve the input extras currently associated with the text view, which
4997 * can be viewed as well as modified.
4998 *
4999 * @param create If true, the extras will be created if they don't already
5000 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07005001 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005002 * @see EditorInfo#extras
5003 * @attr ref android.R.styleable#TextView_editorExtras
5004 */
5005 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005006 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07005007 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005008 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005009 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005010 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005011 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005012 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005013 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005014 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005015 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005016 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005017 }
5018
5019 /**
Yohei Yukawad469f212016-01-21 12:38:09 -08005020 * Change "hint" locales associated with the text view, which will be reported to an IME with
5021 * {@link EditorInfo#hintLocales} when it has focus.
5022 *
5023 * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to
5024 * call {@link InputMethodManager#restartInput(View)}.</p>
5025 * @param hintLocales List of the languages that the user is supposed to switch to no matter
5026 * what input method subtype is currently used. Set {@code null} to clear the current "hint".
5027 * @see #getImeHIntLocales()
5028 * @see android.view.inputmethod.EditorInfo#hintLocales
5029 */
5030 public void setImeHintLocales(@Nullable LocaleList hintLocales) {
5031 createEditorIfNeeded();
5032 mEditor.createInputContentTypeIfNeeded();
5033 mEditor.mInputContentType.imeHintLocales = hintLocales;
5034 }
5035
5036 /**
5037 * @return The current languages list "hint". {@code null} when no "hint" is available.
5038 * @see #setImeHintLocales(LocaleList)
5039 * @see android.view.inputmethod.EditorInfo#hintLocales
5040 */
5041 @Nullable
5042 public LocaleList getImeHintLocales() {
5043 if (mEditor == null) { return null; }
5044 if (mEditor.mInputContentType == null) { return null; }
5045 return mEditor.mInputContentType.imeHintLocales;
5046 }
5047
5048 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005049 * Returns the error message that was set to be displayed with
5050 * {@link #setError}, or <code>null</code> if no error was set
5051 * or if it the error was cleared by the widget after user input.
5052 */
5053 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005054 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005055 }
5056
5057 /**
5058 * Sets the right-hand compound drawable of the TextView to the "error"
5059 * icon and sets an error message that will be displayed in a popup when
5060 * the TextView has focus. The icon and error message will be reset to
5061 * null when any key events cause changes to the TextView's text. If the
5062 * <code>error</code> is <code>null</code>, the error message and icon
5063 * will be cleared.
5064 */
5065 @android.view.RemotableViewMethod
5066 public void setError(CharSequence error) {
5067 if (error == null) {
5068 setError(null, null);
5069 } else {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08005070 Drawable dr = getContext().getDrawable(
5071 com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005072
5073 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
5074 setError(error, dr);
5075 }
5076 }
5077
5078 /**
5079 * Sets the right-hand compound drawable of the TextView to the specified
5080 * icon and sets an error message that will be displayed in a popup when
5081 * the TextView has focus. The icon and error message will be reset to
5082 * null when any key events cause changes to the TextView's text. The
5083 * drawable must already have had {@link Drawable#setBounds} set on it.
5084 * If the <code>error</code> is <code>null</code>, the error message will
5085 * be cleared (and you should provide a <code>null</code> icon as well).
5086 */
5087 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005088 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005089 mEditor.setError(error, icon);
Alan Viverette77e9a282013-09-12 17:16:09 -07005090 notifyViewAccessibilityStateChangedIfNeeded(
5091 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005092 }
5093
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005094 @Override
5095 protected boolean setFrame(int l, int t, int r, int b) {
5096 boolean result = super.setFrame(l, t, r, b);
5097
Gilles Debunne2d373a12012-04-20 15:32:19 -07005098 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005099
Romain Guy986003d2009-03-25 17:42:35 -07005100 restartMarqueeIfNeeded();
5101
5102 return result;
5103 }
5104
5105 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005106 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
5107 mRestartMarquee = false;
5108 startMarquee();
5109 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005110 }
5111
5112 /**
5113 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08005114 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005115 *
5116 * @attr ref android.R.styleable#TextView_maxLength
5117 */
5118 public void setFilters(InputFilter[] filters) {
5119 if (filters == null) {
5120 throw new IllegalArgumentException();
5121 }
5122
5123 mFilters = filters;
5124
5125 if (mText instanceof Editable) {
5126 setFilters((Editable) mText, filters);
5127 }
5128 }
5129
5130 /**
5131 * Sets the list of input filters on the specified Editable,
5132 * and includes mInput in the list if it is an InputFilter.
5133 */
5134 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005135 if (mEditor != null) {
5136 final boolean undoFilter = mEditor.mUndoInputFilter != null;
5137 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
5138 int num = 0;
5139 if (undoFilter) num++;
5140 if (keyFilter) num++;
5141 if (num > 0) {
5142 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005143
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005144 System.arraycopy(filters, 0, nf, 0, filters.length);
5145 num = 0;
5146 if (undoFilter) {
5147 nf[filters.length] = mEditor.mUndoInputFilter;
5148 num++;
5149 }
5150 if (keyFilter) {
5151 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5152 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005153
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005154 e.setFilters(nf);
5155 return;
5156 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005157 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07005158 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005159 }
5160
5161 /**
5162 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07005163 *
5164 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005165 */
5166 public InputFilter[] getFilters() {
5167 return mFilters;
5168 }
5169
5170 /////////////////////////////////////////////////////////////////////////
5171
Philip Milne7b757812012-09-19 18:13:44 -07005172 private int getBoxHeight(Layout l) {
5173 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5174 int padding = (l == mHintLayout) ?
5175 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5176 getExtendedPaddingTop() + getExtendedPaddingBottom();
5177 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5178 }
5179
Gilles Debunned88876a2012-03-16 17:34:04 -07005180 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005181 int voffset = 0;
5182 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5183
5184 Layout l = mLayout;
5185 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5186 l = mHintLayout;
5187 }
5188
5189 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07005190 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005191 int textht = l.getHeight();
5192
5193 if (textht < boxht) {
5194 if (gravity == Gravity.BOTTOM)
5195 voffset = boxht - textht;
5196 else // (gravity == Gravity.CENTER_VERTICAL)
5197 voffset = (boxht - textht) >> 1;
5198 }
5199 }
5200 return voffset;
5201 }
5202
5203 private int getBottomVerticalOffset(boolean forceNormal) {
5204 int voffset = 0;
5205 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5206
5207 Layout l = mLayout;
5208 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5209 l = mHintLayout;
5210 }
5211
5212 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07005213 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005214 int textht = l.getHeight();
5215
5216 if (textht < boxht) {
5217 if (gravity == Gravity.TOP)
5218 voffset = boxht - textht;
5219 else // (gravity == Gravity.CENTER_VERTICAL)
5220 voffset = (boxht - textht) >> 1;
5221 }
5222 }
5223 return voffset;
5224 }
5225
Gilles Debunned88876a2012-03-16 17:34:04 -07005226 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08005227 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005228 invalidateCursor();
5229 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005230 final int horizontalPadding = getCompoundPaddingLeft();
5231 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005232
Gilles Debunne2d373a12012-04-20 15:32:19 -07005233 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005234 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005235 /*
5236 * The reason for this concern about the thickness of the
5237 * cursor and doing the floor/ceil on the coordinates is that
5238 * some EditTexts (notably textfields in the Browser) have
5239 * anti-aliased text where not all the characters are
5240 * necessarily at integer-multiple locations. This should
5241 * make sure the entire cursor gets invalidated instead of
5242 * sometimes missing half a pixel.
5243 */
Neil Fuller33253a42014-10-01 11:55:10 +01005244 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005245 if (thick < 1.0f) {
5246 thick = 1.0f;
5247 }
5248
5249 thick /= 2.0f;
5250
Gilles Debunne83051b82012-02-24 20:01:13 -08005251 // mHighlightPath is guaranteed to be non null at that point.
5252 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005253
Neil Fuller33253a42014-10-01 11:55:10 +01005254 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5255 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5256 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5257 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005258 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005259 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005260 for (int i = 0; i < mEditor.mCursorCount; i++) {
5261 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005262 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5263 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5264 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005265 }
5266 }
5267 }
5268
Gilles Debunned88876a2012-03-16 17:34:04 -07005269 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07005270 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005271
5272 invalidateCursor(where, where, where);
5273 }
5274
5275 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005276 if (a >= 0 || b >= 0 || c >= 0) {
5277 int start = Math.min(Math.min(a, b), c);
5278 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08005279 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005280 }
5281 }
5282
5283 /**
5284 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08005285 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08005286 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005287 if (mLayout == null) {
5288 invalidate();
5289 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005290 int lineStart = mLayout.getLineForOffset(start);
5291 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005292
5293 // This is ridiculous, but the descent from the line above
5294 // can hang down into the line we really want to redraw,
5295 // so we have to invalidate part of the line above to make
5296 // sure everything that needs to be redrawn really is.
5297 // (But not the whole line above, because that would cause
5298 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08005299 if (lineStart > 0) {
5300 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005301 }
5302
Gilles Debunne8615ac92011-11-29 15:25:03 -08005303 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005304
Gilles Debunne8615ac92011-11-29 15:25:03 -08005305 if (start == end)
5306 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005307 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08005308 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005309
Gilles Debunne8615ac92011-11-29 15:25:03 -08005310 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005311
Gilles Debunne83051b82012-02-24 20:01:13 -08005312 // mEditor can be null in case selection is set programmatically.
5313 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005314 for (int i = 0; i < mEditor.mCursorCount; i++) {
5315 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08005316 top = Math.min(top, bounds.top);
5317 bottom = Math.max(bottom, bounds.bottom);
5318 }
5319 }
5320
Gilles Debunne8615ac92011-11-29 15:25:03 -08005321 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005322 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08005323
5324 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08005325 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08005326 left = (int) mLayout.getPrimaryHorizontal(start);
5327 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5328 left += compoundPaddingLeft;
5329 right += compoundPaddingLeft;
5330 } else {
5331 // Rectangle bounding box when the region spans several lines
5332 left = compoundPaddingLeft;
5333 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08005334 }
5335
Gilles Debunne8615ac92011-11-29 15:25:03 -08005336 invalidate(mScrollX + left, verticalPadding + top,
5337 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005338 }
5339 }
5340
5341 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005342 if (!mPreDrawRegistered) {
5343 getViewTreeObserver().addOnPreDrawListener(this);
5344 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005346 }
5347
Craig Stoutf209ef92014-07-02 15:23:16 -07005348 private void unregisterForPreDraw() {
5349 getViewTreeObserver().removeOnPreDrawListener(this);
5350 mPreDrawRegistered = false;
5351 mPreDrawListenerDetached = false;
5352 }
5353
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005354 /**
5355 * {@inheritDoc}
5356 */
5357 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005358 if (mLayout == null) {
5359 assumeLayout();
5360 }
5361
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005362 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07005363 /* This code also provides auto-scrolling when a cursor is moved using a
5364 * CursorController (insertion point or selection limits).
5365 * For selection, ensure start or end is visible depending on controller's state.
5366 */
5367 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08005368 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005369 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5370 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005371 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07005372 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005373
5374 /*
5375 * TODO: This should really only keep the end in view if
5376 * it already was before the text changed. I'm not sure
5377 * of a good way to tell from here if it was.
5378 */
Gilles Debunne60e21862012-01-30 15:04:14 -08005379 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005380 curs = mText.length();
5381 }
5382
5383 if (curs >= 0) {
Raph Leviene048f842013-09-27 13:36:24 -07005384 bringPointIntoView(curs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005385 }
5386 } else {
Raph Leviene048f842013-09-27 13:36:24 -07005387 bringTextIntoView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005388 }
5389
Gilles Debunne64e54a62010-09-07 19:07:17 -07005390 // This has to be checked here since:
5391 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5392 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005393 if (mEditor != null && mEditor.mCreatedWithASelection) {
Andrei Stingaceanu99d3bbd2015-06-30 16:57:03 +01005394 if (mEditor.extractedTextModeWillBeStarted()) {
5395 mEditor.checkFieldAndSelectCurrentWord();
5396 } else {
5397 mEditor.startSelectionActionMode();
5398 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005399 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08005400 }
5401
Craig Stoutf209ef92014-07-02 15:23:16 -07005402 unregisterForPreDraw();
Gilles Debunne2e37d622012-01-27 13:54:00 -08005403
Raph Leviene048f842013-09-27 13:36:24 -07005404 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005405 }
5406
5407 @Override
5408 protected void onAttachedToWindow() {
5409 super.onAttachedToWindow();
5410
5411 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005412
Gilles Debunne2d373a12012-04-20 15:32:19 -07005413 if (mEditor != null) mEditor.onAttachedToWindow();
Craig Stoutf209ef92014-07-02 15:23:16 -07005414
5415 if (mPreDrawListenerDetached) {
5416 getViewTreeObserver().addOnPreDrawListener(this);
5417 mPreDrawListenerDetached = false;
5418 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005419 }
5420
John Reckb14dfe22014-03-06 22:06:20 +00005421 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005422 @Override
John Reckb14dfe22014-03-06 22:06:20 +00005423 protected void onDetachedFromWindowInternal() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08005424 if (mPreDrawRegistered) {
5425 getViewTreeObserver().removeOnPreDrawListener(this);
Craig Stoutf209ef92014-07-02 15:23:16 -07005426 mPreDrawListenerDetached = true;
Gilles Debunne81f08082011-02-17 14:07:19 -08005427 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005428
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005429 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07005430
Gilles Debunne2d373a12012-04-20 15:32:19 -07005431 if (mEditor != null) mEditor.onDetachedFromWindow();
John Reckb14dfe22014-03-06 22:06:20 +00005432
5433 super.onDetachedFromWindowInternal();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005434 }
5435
5436 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08005437 public void onScreenStateChanged(int screenState) {
5438 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005439 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08005440 }
5441
5442 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005443 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005444 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005445 }
5446
5447 @Override
5448 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07005449 return getCompoundPaddingLeft() - mPaddingLeft +
5450 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005451 }
5452
5453 @Override
5454 protected int getTopPaddingOffset() {
5455 return (int) Math.min(0, mShadowDy - mShadowRadius);
5456 }
5457
5458 @Override
5459 protected int getBottomPaddingOffset() {
5460 return (int) Math.max(0, mShadowDy + mShadowRadius);
5461 }
5462
5463 @Override
5464 protected int getRightPaddingOffset() {
Siyamed Sinir217c0f72016-02-01 18:30:02 -08005465 return -(getCompoundPaddingRight() - mPaddingRight) +
Romain Guy076dc9f2009-06-24 17:17:51 -07005466 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005467 }
5468
5469 @Override
5470 protected boolean verifyDrawable(Drawable who) {
5471 final boolean verified = super.verifyDrawable(who);
5472 if (!verified && mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005473 for (Drawable dr : mDrawables.mShowing) {
5474 if (who == dr) {
5475 return true;
5476 }
5477 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005478 }
5479 return verified;
5480 }
5481
5482 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07005483 public void jumpDrawablesToCurrentState() {
5484 super.jumpDrawablesToCurrentState();
5485 if (mDrawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005486 for (Drawable dr : mDrawables.mShowing) {
5487 if (dr != null) {
5488 dr.jumpToCurrentState();
5489 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07005490 }
Dianne Hackborne2136772010-11-04 15:08:59 -07005491 }
5492 }
5493
5494 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07005495 public void invalidateDrawable(Drawable drawable) {
Alan Viverettee6875f12014-02-05 14:05:17 -08005496 boolean handled = false;
5497
Romain Guy3c77d392009-05-20 11:26:50 -07005498 if (verifyDrawable(drawable)) {
5499 final Rect dirty = drawable.getBounds();
5500 int scrollX = mScrollX;
5501 int scrollY = mScrollY;
5502
5503 // IMPORTANT: The coordinates below are based on the coordinates computed
5504 // for each compound drawable in onDraw(). Make sure to update each section
5505 // accordingly.
5506 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07005507 if (drawables != null) {
Alan Viveretteb97d6982015-01-07 16:16:20 -08005508 if (drawable == drawables.mShowing[Drawables.LEFT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005509 final int compoundPaddingTop = getCompoundPaddingTop();
5510 final int compoundPaddingBottom = getCompoundPaddingBottom();
5511 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005512
Romain Guya6cd4e02009-05-20 15:09:21 -07005513 scrollX += mPaddingLeft;
5514 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005515 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005516 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005517 final int compoundPaddingTop = getCompoundPaddingTop();
5518 final int compoundPaddingBottom = getCompoundPaddingBottom();
5519 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07005520
Romain Guya6cd4e02009-05-20 15:09:21 -07005521 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5522 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
Alan Viverettee6875f12014-02-05 14:05:17 -08005523 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005524 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005525 final int compoundPaddingLeft = getCompoundPaddingLeft();
5526 final int compoundPaddingRight = getCompoundPaddingRight();
5527 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005528
Romain Guya6cd4e02009-05-20 15:09:21 -07005529 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5530 scrollY += mPaddingTop;
Alan Viverettee6875f12014-02-05 14:05:17 -08005531 handled = true;
Alan Viveretteb97d6982015-01-07 16:16:20 -08005532 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
Romain Guya6cd4e02009-05-20 15:09:21 -07005533 final int compoundPaddingLeft = getCompoundPaddingLeft();
5534 final int compoundPaddingRight = getCompoundPaddingRight();
5535 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07005536
Romain Guya6cd4e02009-05-20 15:09:21 -07005537 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5538 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
Alan Viverettee6875f12014-02-05 14:05:17 -08005539 handled = true;
Romain Guya6cd4e02009-05-20 15:09:21 -07005540 }
Romain Guy3c77d392009-05-20 11:26:50 -07005541 }
5542
Alan Viverettee6875f12014-02-05 14:05:17 -08005543 if (handled) {
5544 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5545 dirty.right + scrollX, dirty.bottom + scrollY);
5546 }
5547 }
5548
5549 if (!handled) {
5550 super.invalidateDrawable(drawable);
Romain Guy3c77d392009-05-20 11:26:50 -07005551 }
5552 }
5553
5554 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07005555 public boolean hasOverlappingRendering() {
Chris Craik7bcde502013-10-11 12:51:11 -07005556 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
Michael Jurka0931a852013-03-21 16:07:45 +01005557 return ((getBackground() != null && getBackground().getCurrent() != null)
Chris Craik7bcde502013-10-11 12:51:11 -07005558 || mText instanceof Spannable || hasSelection()
5559 || isHorizontalFadingEdgeEnabled());
Chet Haasedb8c9a62012-03-21 18:54:18 -07005560 }
5561
Gilles Debunne86b9c782010-11-11 10:43:48 -08005562 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08005563 *
Joe Malin10d96952013-05-29 17:49:09 -07005564 * Returns the state of the {@code textIsSelectable} flag (See
5565 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5566 * to allow users to select and copy text in a non-editable TextView, the content of an
5567 * {@link EditText} can always be selected, independently of the value of this flag.
5568 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08005569 *
5570 * @return True if the text displayed in this TextView can be selected by the user.
5571 *
5572 * @attr ref android.R.styleable#TextView_textIsSelectable
5573 */
5574 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005575 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005576 }
5577
5578 /**
Joe Malin10d96952013-05-29 17:49:09 -07005579 * Sets whether the content of this view is selectable by the user. The default is
5580 * {@code false}, meaning that the content is not selectable.
5581 * <p>
5582 * When you use a TextView to display a useful piece of information to the user (such as a
5583 * contact's address), make it selectable, so that the user can select and copy its
5584 * content. You can also use set the XML attribute
5585 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5586 * <p>
5587 * When you call this method to set the value of {@code textIsSelectable}, it sets
5588 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5589 * and {@code longClickable} to the same value. These flags correspond to the attributes
5590 * {@link android.R.styleable#View_focusable android:focusable},
5591 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5592 * {@link android.R.styleable#View_clickable android:clickable}, and
5593 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5594 * flags to a state you had set previously, call one or more of the following methods:
5595 * {@link #setFocusable(boolean) setFocusable()},
5596 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5597 * {@link #setClickable(boolean) setClickable()} or
5598 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08005599 *
Joe Malin10d96952013-05-29 17:49:09 -07005600 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08005601 */
5602 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005603 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08005604
Gilles Debunne5fae9962012-05-08 14:53:20 -07005605 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005606 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08005607
Gilles Debunne2d373a12012-04-20 15:32:19 -07005608 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08005609 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005610 setFocusable(selectable);
5611 setClickable(selectable);
5612 setLongClickable(selectable);
5613
Gilles Debunne60e21862012-01-30 15:04:14 -08005614 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08005615
5616 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07005617 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08005618
5619 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07005620 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08005621 }
5622
5623 @Override
5624 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08005625 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005626
Gilles Debunnefb817032011-01-13 13:52:49 -08005627 if (mSingleLine) {
5628 drawableState = super.onCreateDrawableState(extraSpace);
5629 } else {
5630 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005631 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5632 }
5633
Gilles Debunne60e21862012-01-30 15:04:14 -08005634 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08005635 // Disable pressed state, which was introduced when TextView was made clickable.
5636 // Prevents text color change.
5637 // setClickable(false) would have a similar effect, but it also disables focus changes
5638 // and long press actions, which are both needed by text selection.
5639 final int length = drawableState.length;
5640 for (int i = 0; i < length; i++) {
5641 if (drawableState[i] == R.attr.state_pressed) {
5642 final int[] nonPressedState = new int[length - 1];
5643 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5644 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5645 return nonPressedState;
5646 }
5647 }
5648 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08005649
Gilles Debunne86b9c782010-11-11 10:43:48 -08005650 return drawableState;
5651 }
5652
Gilles Debunne83051b82012-02-24 20:01:13 -08005653 private Path getUpdatedHighlightPath() {
5654 Path highlight = null;
5655 Paint highlightPaint = mHighlightPaint;
5656
5657 final int selStart = getSelectionStart();
5658 final int selEnd = getSelectionEnd();
5659 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5660 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005661 if (mEditor != null && mEditor.isCursorVisible() &&
5662 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07005663 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08005664 if (mHighlightPathBogus) {
5665 if (mHighlightPath == null) mHighlightPath = new Path();
5666 mHighlightPath.reset();
5667 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005668 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08005669 mHighlightPathBogus = false;
5670 }
5671
5672 // XXX should pass to skin instead of drawing directly
5673 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005674 highlightPaint.setStyle(Paint.Style.STROKE);
5675 highlight = mHighlightPath;
5676 }
5677 } else {
5678 if (mHighlightPathBogus) {
5679 if (mHighlightPath == null) mHighlightPath = new Path();
5680 mHighlightPath.reset();
5681 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5682 mHighlightPathBogus = false;
5683 }
5684
5685 // XXX should pass to skin instead of drawing directly
5686 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08005687 highlightPaint.setStyle(Paint.Style.FILL);
5688
5689 highlight = mHighlightPath;
5690 }
5691 }
5692 return highlight;
5693 }
5694
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005695 /**
5696 * @hide
5697 */
5698 public int getHorizontalOffsetForDrawables() {
5699 return 0;
5700 }
5701
Romain Guyc4d8eb62010-08-18 20:48:33 -07005702 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005703 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005704 restartMarqueeIfNeeded();
5705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005706 // Draw the background for this view
5707 super.onDraw(canvas);
5708
5709 final int compoundPaddingLeft = getCompoundPaddingLeft();
5710 final int compoundPaddingTop = getCompoundPaddingTop();
5711 final int compoundPaddingRight = getCompoundPaddingRight();
5712 final int compoundPaddingBottom = getCompoundPaddingBottom();
5713 final int scrollX = mScrollX;
5714 final int scrollY = mScrollY;
5715 final int right = mRight;
5716 final int left = mLeft;
5717 final int bottom = mBottom;
5718 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005719 final boolean isLayoutRtl = isLayoutRtl();
5720 final int offset = getHorizontalOffsetForDrawables();
5721 final int leftOffset = isLayoutRtl ? 0 : offset;
5722 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005723
5724 final Drawables dr = mDrawables;
5725 if (dr != null) {
5726 /*
5727 * Compound, not extended, because the icon is not clipped
5728 * if the text height is smaller.
5729 */
5730
5731 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5732 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5733
Romain Guy3c77d392009-05-20 11:26:50 -07005734 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5735 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005736 if (dr.mShowing[Drawables.LEFT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005737 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005738 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005739 scrollY + compoundPaddingTop +
5740 (vspace - dr.mDrawableHeightLeft) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005741 dr.mShowing[Drawables.LEFT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005742 canvas.restore();
5743 }
5744
Romain Guy3c77d392009-05-20 11:26:50 -07005745 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5746 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005747 if (dr.mShowing[Drawables.RIGHT] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005748 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005749 canvas.translate(scrollX + right - left - mPaddingRight
5750 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005751 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005752 dr.mShowing[Drawables.RIGHT].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005753 canvas.restore();
5754 }
5755
Romain Guy3c77d392009-05-20 11:26:50 -07005756 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5757 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005758 if (dr.mShowing[Drawables.TOP] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005759 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005760 canvas.translate(scrollX + compoundPaddingLeft +
5761 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005762 dr.mShowing[Drawables.TOP].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005763 canvas.restore();
5764 }
5765
Romain Guy3c77d392009-05-20 11:26:50 -07005766 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5767 // Make sure to update invalidateDrawable() when changing this code.
Alan Viveretteb97d6982015-01-07 16:16:20 -08005768 if (dr.mShowing[Drawables.BOTTOM] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005769 canvas.save();
5770 canvas.translate(scrollX + compoundPaddingLeft +
5771 (hspace - dr.mDrawableWidthBottom) / 2,
5772 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
Alan Viveretteb97d6982015-01-07 16:16:20 -08005773 dr.mShowing[Drawables.BOTTOM].draw(canvas);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005774 canvas.restore();
5775 }
5776 }
5777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005778 int color = mCurTextColor;
5779
5780 if (mLayout == null) {
5781 assumeLayout();
5782 }
5783
5784 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005785
5786 if (mHint != null && mText.length() == 0) {
5787 if (mHintTextColor != null) {
5788 color = mCurHintTextColor;
5789 }
5790
5791 layout = mHintLayout;
5792 }
5793
5794 mTextPaint.setColor(color);
5795 mTextPaint.drawableState = getDrawableState();
5796
5797 canvas.save();
5798 /* Would be faster if we didn't have to do this. Can we chop the
5799 (displayable) text so that we don't need to do this ever?
5800 */
5801
5802 int extendedPaddingTop = getExtendedPaddingTop();
5803 int extendedPaddingBottom = getExtendedPaddingBottom();
5804
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005805 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5806 final int maxScrollY = mLayout.getHeight() - vspace;
5807
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005808 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005809 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
Siyamed Sinir217c0f72016-02-01 18:30:02 -08005810 float clipRight = right - left - getCompoundPaddingRight() + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005811 float clipBottom = bottom - top + scrollY -
5812 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005813
5814 if (mShadowRadius != 0) {
5815 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5816 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5817
5818 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5819 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5820 }
5821
5822 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5823
5824 int voffsetText = 0;
5825 int voffsetCursor = 0;
5826
5827 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005828 /* shortcircuit calling getVerticaOffset() */
5829 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5830 voffsetText = getVerticalOffset(false);
5831 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005832 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005833 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005834
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005835 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005836 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005837 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5838 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005839 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005840 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005841 final int width = mRight - mLeft;
5842 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5843 final float dx = mLayout.getLineRight(0) - (width - padding);
Michael Lekman89bc4132012-01-11 14:27:52 +01005844 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005845 }
5846
5847 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005848 final float dx = -mMarquee.getScroll();
Michael Lekman89bc4132012-01-11 14:27:52 +01005849 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005850 }
5851 }
5852
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005853 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005854
Gilles Debunne83051b82012-02-24 20:01:13 -08005855 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005856 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005857 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005858 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005859 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005860 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005861
Gilles Debunned88876a2012-03-16 17:34:04 -07005862 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Michael Lekman89bc4132012-01-11 14:27:52 +01005863 final float dx = mMarquee.getGhostOffset();
5864 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005865 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005866 }
5867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005868 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005869 }
5870
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005871 @Override
5872 public void getFocusedRect(Rect r) {
5873 if (mLayout == null) {
5874 super.getFocusedRect(r);
5875 return;
5876 }
5877
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005878 int selEnd = getSelectionEnd();
5879 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005880 super.getFocusedRect(r);
5881 return;
5882 }
5883
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005884 int selStart = getSelectionStart();
5885 if (selStart < 0 || selStart >= selEnd) {
5886 int line = mLayout.getLineForOffset(selEnd);
5887 r.top = mLayout.getLineTop(line);
5888 r.bottom = mLayout.getLineBottom(line);
5889 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5890 r.right = r.left + 4;
5891 } else {
5892 int lineStart = mLayout.getLineForOffset(selStart);
5893 int lineEnd = mLayout.getLineForOffset(selEnd);
5894 r.top = mLayout.getLineTop(lineStart);
5895 r.bottom = mLayout.getLineBottom(lineEnd);
5896 if (lineStart == lineEnd) {
5897 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5898 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5899 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005900 // Selection extends across multiple lines -- make the focused
5901 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005902 if (mHighlightPathBogus) {
5903 if (mHighlightPath == null) mHighlightPath = new Path();
5904 mHighlightPath.reset();
5905 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5906 mHighlightPathBogus = false;
5907 }
5908 synchronized (TEMP_RECTF) {
5909 mHighlightPath.computeBounds(TEMP_RECTF, true);
5910 r.left = (int)TEMP_RECTF.left-1;
5911 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005912 }
5913 }
5914 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005915
5916 // Adjust for padding and gravity.
5917 int paddingLeft = getCompoundPaddingLeft();
5918 int paddingTop = getExtendedPaddingTop();
5919 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5920 paddingTop += getVerticalOffset(false);
5921 }
5922 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005923 int paddingBottom = getExtendedPaddingBottom();
5924 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005925 }
5926
5927 /**
5928 * Return the number of lines of text, or 0 if the internal Layout has not
5929 * been built.
5930 */
5931 public int getLineCount() {
5932 return mLayout != null ? mLayout.getLineCount() : 0;
5933 }
5934
5935 /**
5936 * Return the baseline for the specified line (0...getLineCount() - 1)
5937 * If bounds is not null, return the top, left, right, bottom extents
5938 * of the specified line in it. If the internal Layout has not been built,
5939 * return 0 and set bounds to (0, 0, 0, 0)
5940 * @param line which line to examine (0..getLineCount() - 1)
5941 * @param bounds Optional. If not null, it returns the extent of the line
5942 * @return the Y-coordinate of the baseline
5943 */
5944 public int getLineBounds(int line, Rect bounds) {
5945 if (mLayout == null) {
5946 if (bounds != null) {
5947 bounds.set(0, 0, 0, 0);
5948 }
5949 return 0;
5950 }
5951 else {
5952 int baseline = mLayout.getLineBounds(line, bounds);
5953
5954 int voffset = getExtendedPaddingTop();
5955 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5956 voffset += getVerticalOffset(true);
5957 }
5958 if (bounds != null) {
5959 bounds.offset(getCompoundPaddingLeft(), voffset);
5960 }
5961 return baseline + voffset;
5962 }
5963 }
5964
5965 @Override
5966 public int getBaseline() {
5967 if (mLayout == null) {
5968 return super.getBaseline();
5969 }
5970
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07005971 return getBaselineOffset() + mLayout.getLineBaseline(0);
5972 }
5973
5974 int getBaselineOffset() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005975 int voffset = 0;
5976 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5977 voffset = getVerticalOffset(true);
5978 }
5979
Philip Milne7b757812012-09-19 18:13:44 -07005980 if (isLayoutModeOptical(mParent)) {
5981 voffset -= getOpticalInsets().top;
5982 }
5983
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07005984 return getExtendedPaddingTop() + voffset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005985 }
5986
Romain Guyf2fc4602011-07-19 15:20:03 -07005987 /**
5988 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005989 */
5990 @Override
5991 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005992 if (mLayout == null) return 0;
5993
Romain Guyf2fc4602011-07-19 15:20:03 -07005994 int voffset = 0;
5995 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5996 voffset = getVerticalOffset(true);
5997 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005998
Romain Guyf2fc4602011-07-19 15:20:03 -07005999 if (offsetRequired) voffset += getTopPaddingOffset();
6000
6001 return getExtendedPaddingTop() + voffset;
6002 }
6003
6004 /**
6005 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07006006 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07006007 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07006008 protected int getFadeHeight(boolean offsetRequired) {
6009 return mLayout != null ? mLayout.getHeight() : 0;
6010 }
6011
Jun Mukai1db53972015-09-11 18:08:31 -07006012 @Override
Jun Mukaid4eaef72015-10-30 15:54:33 -07006013 public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
Keisuke Kuroyanagi366a8862015-12-15 16:34:35 +09006014 if (mText instanceof Spannable && mLinksClickable) {
6015 final int offset = getOffsetForPosition(x, y);
6016 final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
6017 ClickableSpan.class);
6018 if (clickables.length > 0) {
6019 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_HAND);
6020 }
6021 }
Jun Mukai1db53972015-09-11 18:08:31 -07006022 if (isTextSelectable() || isTextEditable()) {
Jun Mukaid4eaef72015-10-30 15:54:33 -07006023 return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_TEXT);
Jun Mukai1db53972015-09-11 18:08:31 -07006024 }
Jun Mukaid4eaef72015-10-30 15:54:33 -07006025 return super.getPointerIcon(event, x, y);
Jun Mukai1db53972015-09-11 18:08:31 -07006026 }
6027
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006028 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006029 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
Yohei Yukawa38940aa2015-06-24 00:20:24 -07006030 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
6031 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
6032 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
6033 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
6034 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006035 }
6036 return super.onKeyPreIme(keyCode, event);
6037 }
6038
Yohei Yukawa38940aa2015-06-24 00:20:24 -07006039 /**
6040 * @hide
6041 */
6042 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
6043 // Do nothing unless mEditor is in text action mode.
6044 if (mEditor == null || mEditor.mTextActionMode == null) {
6045 return false;
6046 }
6047
6048 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
6049 KeyEvent.DispatcherState state = getKeyDispatcherState();
6050 if (state != null) {
6051 state.startTracking(event, this);
6052 }
6053 return true;
6054 } else if (event.getAction() == KeyEvent.ACTION_UP) {
6055 KeyEvent.DispatcherState state = getKeyDispatcherState();
6056 if (state != null) {
6057 state.handleUpEvent(event);
6058 }
6059 if (event.isTracking() && !event.isCanceled()) {
6060 stopTextActionMode();
6061 return true;
6062 }
6063 }
6064 return false;
6065 }
6066
Gilles Debunne4a7199a2011-07-11 14:58:27 -07006067 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006068 public boolean onKeyDown(int keyCode, KeyEvent event) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006069 final int which = doKeyDown(keyCode, event, null);
6070 if (which == KEY_EVENT_NOT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006071 return super.onKeyDown(keyCode, event);
6072 }
6073
6074 return true;
6075 }
6076
6077 @Override
6078 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006079 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006080 final int which = doKeyDown(keyCode, down, event);
6081 if (which == KEY_EVENT_NOT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006082 // Go through default dispatching.
6083 return super.onKeyMultiple(keyCode, repeatCount, event);
6084 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006085 if (which == KEY_EVENT_HANDLED) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006086 // Consumed the whole thing.
6087 return true;
6088 }
6089
6090 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006091
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006092 // We are going to dispatch the remaining events to either the input
6093 // or movement method. To do this, we will just send a repeated stream
6094 // of down and up events until we have done the complete repeatCount.
6095 // It would be nice if those interfaces had an onKeyMultiple() method,
6096 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07006097 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006098 if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006099 // mEditor and mEditor.mInput are not null from doKeyDown
6100 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006101 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006102 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
6103 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006104 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006105 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006106
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006107 } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
Gilles Debunne60e21862012-01-30 15:04:14 -08006108 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006109 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6110 while (--repeatCount > 0) {
6111 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
6112 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
6113 }
6114 }
6115
6116 return true;
6117 }
6118
6119 /**
6120 * Returns true if pressing ENTER in this field advances focus instead
6121 * of inserting the character. This is true mostly in single-line fields,
6122 * but also in mail addresses and subjects which will display on multiple
6123 * lines but where it doesn't make sense to insert newlines.
6124 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08006125 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08006126 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 return false;
6128 }
6129
6130 if (mSingleLine) {
6131 return true;
6132 }
6133
Gilles Debunne2d373a12012-04-20 15:32:19 -07006134 if (mEditor != null &&
6135 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6136 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006137 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
6138 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006139 return true;
6140 }
6141 }
6142
6143 return false;
6144 }
6145
Jeff Brown4e6319b2010-12-13 10:36:51 -08006146 /**
6147 * Returns true if pressing TAB in this field advances focus instead
6148 * of inserting the character. Insert tabs only in multi-line editors.
6149 */
6150 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006151 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
6152 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
6153 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
6154 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
6155 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6156 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006157 }
6158 }
6159 return true;
6160 }
6161
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006162 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6163 if (!isEnabled()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006164 return KEY_EVENT_NOT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006165 }
6166
Michael Wright3a7e4832013-02-11 15:55:50 -08006167 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6168 // While this shouldn't be necessary because any time we're preventing default movement we
6169 // should be restricting the focus to remain within this view, thus we'll also receive
6170 // the key up event, occasionally key up events will get dropped and we don't want to
6171 // prevent the user from traversing out of this on the next key down.
6172 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6173 mPreventDefaultMovement = false;
6174 }
6175
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006176 switch (keyCode) {
6177 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006178 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006179 // When mInputContentType is set, we know that we are
6180 // running in a "modern" cupcake environment, so don't need
6181 // to worry about the application trying to capture
6182 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006183 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006184 // If there is an action listener, given them a
6185 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07006186 if (mEditor.mInputContentType.onEditorActionListener != null &&
6187 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07006188 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006189 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07006190 // We are consuming the enter key for them.
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006191 return KEY_EVENT_HANDLED;
The Android Open Source Project10592532009-03-18 17:39:46 -07006192 }
6193 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006194
The Android Open Source Project10592532009-03-18 17:39:46 -07006195 // If our editor should move focus when enter is pressed, or
6196 // this is a generated event from an IME action button, then
6197 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08006198 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07006199 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07006200 if (hasOnClickListeners()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006201 return KEY_EVENT_NOT_HANDLED;
Leon Scroggins7014b122011-01-11 15:17:34 -05006202 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006203 return KEY_EVENT_HANDLED;
The Android Open Source Project10592532009-03-18 17:39:46 -07006204 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006205 }
The Android Open Source Project10592532009-03-18 17:39:46 -07006206 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006208 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006209 if (event.hasNoModifiers()) {
6210 if (shouldAdvanceFocusOnEnter()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006211 return KEY_EVENT_NOT_HANDLED;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006212 }
6213 }
6214 break;
6215
6216 case KeyEvent.KEYCODE_TAB:
6217 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6218 if (shouldAdvanceFocusOnTab()) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006219 return KEY_EVENT_NOT_HANDLED;
Jeff Brown4e6319b2010-12-13 10:36:51 -08006220 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006221 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006222 break;
6223
6224 // Has to be done on key down (and not on key up) to correctly be intercepted.
6225 case KeyEvent.KEYCODE_BACK:
Clara Bayarri7938cdb2015-06-02 20:03:45 +01006226 if (mEditor != null && mEditor.mTextActionMode != null) {
6227 stopTextActionMode();
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006228 return KEY_EVENT_HANDLED;
Gilles Debunne64e54a62010-09-07 19:07:17 -07006229 }
6230 break;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006231
6232 case KeyEvent.KEYCODE_CUT:
6233 if (event.hasNoModifiers() && canCut()) {
6234 if (onTextContextMenuItem(ID_CUT)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006235 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006236 }
6237 }
6238 break;
6239
6240 case KeyEvent.KEYCODE_COPY:
6241 if (event.hasNoModifiers() && canCopy()) {
6242 if (onTextContextMenuItem(ID_COPY)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006243 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006244 }
6245 }
6246 break;
6247
6248 case KeyEvent.KEYCODE_PASTE:
6249 if (event.hasNoModifiers() && canPaste()) {
6250 if (onTextContextMenuItem(ID_PASTE)) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006251 return KEY_EVENT_HANDLED;
Keisuke Kuroyanagib3163ed2015-10-28 14:27:00 +09006252 }
6253 }
6254 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006255 }
6256
Gilles Debunne2d373a12012-04-20 15:32:19 -07006257 if (mEditor != null && mEditor.mKeyListener != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006258 boolean doDown = true;
6259 if (otherEvent != null) {
6260 try {
6261 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006262 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6263 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006264 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006265 doDown = false;
6266 if (handled) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006267 return KEY_EVENT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006268 }
6269 } catch (AbstractMethodError e) {
6270 // onKeyOther was added after 1.0, so if it isn't
6271 // implemented we need to try to dispatch as a regular down.
6272 } finally {
6273 endBatchEdit();
6274 }
6275 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006276
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006277 if (doDown) {
6278 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006279 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6280 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006281 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08006282 hideErrorIfUnchanged();
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006283 if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006284 }
6285 }
6286
6287 // bug 650865: sometimes we get a key event before a layout.
6288 // don't try to move around if we don't know the layout.
6289
6290 if (mMovement != null && mLayout != null) {
6291 boolean doDown = true;
6292 if (otherEvent != null) {
6293 try {
6294 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6295 otherEvent);
6296 doDown = false;
6297 if (handled) {
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006298 return KEY_EVENT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006299 }
6300 } catch (AbstractMethodError e) {
6301 // onKeyOther was added after 1.0, so if it isn't
6302 // implemented we need to try to dispatch as a regular down.
6303 }
6304 }
6305 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08006306 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6307 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6308 mPreventDefaultMovement = true;
6309 }
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006310 return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD;
Michael Wright3a7e4832013-02-11 15:55:50 -08006311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006312 }
6313 }
6314
Keisuke Kuroyanagi2b46e652015-10-30 15:56:33 +09006315 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ?
6316 KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006317 }
6318
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006319 /**
6320 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6321 * can be recorded.
6322 * @hide
6323 */
6324 public void resetErrorChangedFlag() {
6325 /*
6326 * Keep track of what the error was before doing the input
6327 * so that if an input filter changed the error, we leave
6328 * that error showing. Otherwise, we take down whatever
6329 * error was showing when the user types something.
6330 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07006331 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006332 }
6333
6334 /**
6335 * @hide
6336 */
6337 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006338 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08006339 setError(null, null);
6340 }
6341 }
6342
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006343 @Override
6344 public boolean onKeyUp(int keyCode, KeyEvent event) {
6345 if (!isEnabled()) {
6346 return super.onKeyUp(keyCode, event);
6347 }
6348
Michael Wright3a7e4832013-02-11 15:55:50 -08006349 if (!KeyEvent.isModifierKey(keyCode)) {
6350 mPreventDefaultMovement = false;
6351 }
6352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006353 switch (keyCode) {
6354 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006355 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006356 /*
6357 * If there is a click listener, just call through to
6358 * super, which will invoke it.
6359 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08006360 * If there isn't a click listener, try to show the soft
6361 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006362 * call performClick(), but that won't do anything in
6363 * this case.)
6364 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006365 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006366 if (mMovement != null && mText instanceof Editable
6367 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08006368 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09006369 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07006370 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09006371 imm.showSoftInput(this, 0);
6372 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08006373 }
6374 }
6375 }
6376 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006377
Jeff Brown4e6319b2010-12-13 10:36:51 -08006378 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08006379 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006380 if (mEditor != null && mEditor.mInputContentType != null
6381 && mEditor.mInputContentType.onEditorActionListener != null
6382 && mEditor.mInputContentType.enterDown) {
6383 mEditor.mInputContentType.enterDown = false;
6384 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08006385 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006386 return true;
6387 }
6388 }
6389
Jeff Brown4e6319b2010-12-13 10:36:51 -08006390 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6391 || shouldAdvanceFocusOnEnter()) {
6392 /*
6393 * If there is a click listener, just call through to
6394 * super, which will invoke it.
6395 *
6396 * If there isn't a click listener, try to advance focus,
6397 * but still call through to super, which will reset the
6398 * pressed state and longpress state. (It will also
6399 * call performClick(), but that won't do anything in
6400 * this case.)
6401 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08006402 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006403 View v = focusSearch(FOCUS_DOWN);
6404
6405 if (v != null) {
6406 if (!v.requestFocus(FOCUS_DOWN)) {
6407 throw new IllegalStateException(
6408 "focus search returned a view " +
6409 "that wasn't able to take focus!");
6410 }
6411
6412 /*
6413 * Return true because we handled the key; super
6414 * will return false because there was no click
6415 * listener.
6416 */
6417 super.onKeyUp(keyCode, event);
6418 return true;
6419 } else if ((event.getFlags()
6420 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6421 // No target for next focus, but make sure the IME
6422 // if this came from it.
6423 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08006424 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08006425 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6426 }
6427 }
6428 }
6429 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006430 return super.onKeyUp(keyCode, event);
6431 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07006432 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006433 }
6434
Gilles Debunne2d373a12012-04-20 15:32:19 -07006435 if (mEditor != null && mEditor.mKeyListener != null)
6436 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006437 return true;
6438
6439 if (mMovement != null && mLayout != null)
6440 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6441 return true;
6442
6443 return super.onKeyUp(keyCode, event);
6444 }
6445
Gilles Debunnec1714022012-01-17 13:59:23 -08006446 @Override
6447 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006448 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006449 }
Gilles Debunneb062e812011-09-27 14:58:37 -07006450
Gilles Debunnec1714022012-01-17 13:59:23 -08006451 @Override
6452 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03006453 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006454 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08006455 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006456 if (mEditor.mInputContentType != null) {
6457 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6458 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6459 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6460 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6461 outAttrs.extras = mEditor.mInputContentType.extras;
Yohei Yukawad469f212016-01-21 12:38:09 -08006462 outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006463 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006464 outAttrs.imeOptions = EditorInfo.IME_NULL;
Yohei Yukawad469f212016-01-21 12:38:09 -08006465 outAttrs.hintLocales = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006466 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006467 if (focusSearch(FOCUS_DOWN) != null) {
6468 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6469 }
6470 if (focusSearch(FOCUS_UP) != null) {
6471 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6472 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006473 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6474 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07006475 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006476 // An action has not been set, but the enter key will move to
6477 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006478 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006479 } else {
6480 // An action has not been set, and there is no focus to move
6481 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006482 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08006483 }
6484 if (!shouldAdvanceFocusOnEnter()) {
6485 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006486 }
6487 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08006488 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07006489 // Multi-line text editors should always show an enter key.
6490 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6491 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006492 outAttrs.hintText = mHint;
6493 if (mText instanceof Editable) {
6494 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07006495 outAttrs.initialSelStart = getSelectionStart();
6496 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08006497 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006498 return ic;
6499 }
6500 }
6501 return null;
6502 }
6503
6504 /**
6505 * If this TextView contains editable content, extract a portion of it
6506 * based on the information in <var>request</var> in to <var>outText</var>.
6507 * @return Returns true if the text was successfully extracted, else false.
6508 */
Gilles Debunned88876a2012-03-16 17:34:04 -07006509 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07006510 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07006511 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006512 }
Viktor Yakovel964be412010-02-17 08:35:57 +01006513
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006514 /**
6515 * This is used to remove all style-impacting spans from text before new
6516 * extracted text is being replaced into it, so that we don't have any
6517 * lingering spans applied during the replace.
6518 */
6519 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6520 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6521 int i = spans.length;
6522 while (i > 0) {
6523 i--;
6524 spannable.removeSpan(spans[i]);
6525 }
6526 }
Gilles Debunned88876a2012-03-16 17:34:04 -07006527
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006528 /**
6529 * Apply to this text view the given extracted text, as previously
6530 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6531 */
6532 public void setExtractedText(ExtractedText text) {
6533 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006534 if (text.text != null) {
6535 if (content == null) {
6536 setText(text.text, TextView.BufferType.EDITABLE);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006537 } else {
Andrei Stingaceanucfa13a72015-07-08 15:07:59 +01006538 int start = 0;
6539 int end = content.length();
6540
6541 if (text.partialStartOffset >= 0) {
6542 final int N = content.length();
6543 start = text.partialStartOffset;
6544 if (start > N) start = N;
6545 end = text.partialEndOffset;
6546 if (end > N) end = N;
6547 }
6548
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006549 removeParcelableSpans(content, start, end);
Andrei Stingaceanucfa13a72015-07-08 15:07:59 +01006550 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6551 if (text.text instanceof Spanned) {
6552 // OK to copy spans only.
6553 TextUtils.copySpansFrom((Spanned) text.text, start, end,
6554 Object.class, content, start);
6555 }
6556 } else {
6557 content.replace(start, end, text.text);
6558 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006559 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006560 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006561
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006562 // Now set the selection position... make sure it is in range, to
6563 // avoid crashes. If this is a partial update, it is possible that
6564 // the underlying text may have changed, causing us problems here.
6565 // Also we just don't want to trust clients to do the right thing.
6566 Spannable sp = (Spannable)getText();
6567 final int N = sp.length();
6568 int start = text.selectionStart;
6569 if (start < 0) start = 0;
6570 else if (start > N) start = N;
6571 int end = text.selectionEnd;
6572 if (end < 0) end = 0;
6573 else if (end > N) end = N;
6574 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006575
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07006576 // Finally, update the selection mode.
6577 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6578 MetaKeyKeyListener.startSelecting(this, sp);
6579 } else {
6580 MetaKeyKeyListener.stopSelecting(this, sp);
6581 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006582 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006583
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006584 /**
6585 * @hide
6586 */
6587 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006588 if (mEditor.mInputMethodState != null) {
6589 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006590 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006591 // This would stop a possible selection mode, but no such mode is started in case
6592 // extracted mode will start. Some text is selected though, and will trigger an action mode
6593 // in the extracted view.
Mady Mellora2861452015-06-25 08:40:27 -07006594 mEditor.hideCursorAndSpanControllers();
Clara Bayarri7938cdb2015-06-02 20:03:45 +01006595 stopTextActionMode();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006596 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07006597
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006598 /**
6599 * Called by the framework in response to a text completion from
6600 * the current input method, provided by it calling
6601 * {@link InputConnection#commitCompletion
6602 * InputConnection.commitCompletion()}. The default implementation does
6603 * nothing; text views that are supporting auto-completion should override
6604 * this to do their desired behavior.
6605 *
6606 * @param text The auto complete text the user has selected.
6607 */
6608 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006609 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006610 }
6611
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006612 /**
6613 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6614 * a dictionnary) from the current input method, provided by it calling
6615 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6616 * implementation flashes the background of the corrected word to provide feedback to the user.
6617 *
6618 * @param info The auto correct info about the text that was corrected.
6619 */
6620 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006621 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08006622 }
6623
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006624 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006625 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006626 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006627
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006628 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07006629 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006630 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006631
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006632 /**
6633 * Called by the framework in response to a request to begin a batch
6634 * of edit operations through a call to link {@link #beginBatchEdit()}.
6635 */
6636 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006637 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006638 }
Gilles Debunne60e21862012-01-30 15:04:14 -08006639
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006640 /**
6641 * Called by the framework in response to a request to end a batch
6642 * of edit operations through a call to link {@link #endBatchEdit}.
6643 */
6644 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08006645 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006646 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006648 /**
6649 * Called by the framework in response to a private command from the
6650 * current method, provided by it calling
6651 * {@link InputConnection#performPrivateCommand
6652 * InputConnection.performPrivateCommand()}.
6653 *
6654 * @param action The action name of the command.
6655 * @param data Any additional data for the command. This may be null.
6656 * @return Return true if you handled the command, else false.
6657 */
6658 public boolean onPrivateIMECommand(String action, Bundle data) {
6659 return false;
6660 }
6661
6662 private void nullLayouts() {
6663 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6664 mSavedLayout = (BoringLayout) mLayout;
6665 }
6666 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6667 mSavedHintLayout = (BoringLayout) mHintLayout;
6668 }
6669
Adam Powell282e3772011-08-30 16:51:11 -07006670 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07006671
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08006672 mBoring = mHintBoring = null;
6673
Gilles Debunne77f18b02010-10-22 14:28:25 -07006674 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006675 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006676 }
6677
6678 /**
6679 * Make a new Layout based on the already-measured size of the view,
6680 * on the assumption that it was measured correctly at some point.
6681 */
6682 private void assumeLayout() {
6683 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6684
6685 if (width < 1) {
6686 width = 0;
6687 }
6688
6689 int physicalWidth = width;
6690
6691 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08006692 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006693 }
6694
6695 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6696 physicalWidth, false);
6697 }
6698
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006699 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006700 Layout.Alignment alignment;
6701 switch (getTextAlignment()) {
6702 case TEXT_ALIGNMENT_GRAVITY:
6703 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6704 case Gravity.START:
6705 alignment = Layout.Alignment.ALIGN_NORMAL;
6706 break;
6707 case Gravity.END:
6708 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6709 break;
6710 case Gravity.LEFT:
6711 alignment = Layout.Alignment.ALIGN_LEFT;
6712 break;
6713 case Gravity.RIGHT:
6714 alignment = Layout.Alignment.ALIGN_RIGHT;
6715 break;
6716 case Gravity.CENTER_HORIZONTAL:
6717 alignment = Layout.Alignment.ALIGN_CENTER;
6718 break;
6719 default:
6720 alignment = Layout.Alignment.ALIGN_NORMAL;
6721 break;
6722 }
6723 break;
6724 case TEXT_ALIGNMENT_TEXT_START:
6725 alignment = Layout.Alignment.ALIGN_NORMAL;
6726 break;
6727 case TEXT_ALIGNMENT_TEXT_END:
6728 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6729 break;
6730 case TEXT_ALIGNMENT_CENTER:
6731 alignment = Layout.Alignment.ALIGN_CENTER;
6732 break;
6733 case TEXT_ALIGNMENT_VIEW_START:
6734 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6735 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6736 break;
6737 case TEXT_ALIGNMENT_VIEW_END:
6738 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6739 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6740 break;
6741 case TEXT_ALIGNMENT_INHERIT:
6742 // This should never happen as we have already resolved the text alignment
6743 // but better safe than sorry so we just fall through
6744 default:
6745 alignment = Layout.Alignment.ALIGN_NORMAL;
6746 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006747 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08006748 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006749 }
6750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006751 /**
6752 * The width passed in is now the desired layout width,
6753 * not the full view width with padding.
6754 * {@hide}
6755 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07006756 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006757 BoringLayout.Metrics boring,
6758 BoringLayout.Metrics hintBoring,
6759 int ellipsisWidth, boolean bringIntoView) {
6760 stopMarquee();
6761
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006762 // Update "old" cached values
6763 mOldMaximum = mMaximum;
6764 mOldMaxMode = mMaxMode;
6765
Gilles Debunne83051b82012-02-24 20:01:13 -08006766 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006767
Gilles Debunne287d6c62011-10-05 18:22:11 -07006768 if (wantWidth < 0) {
6769 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006770 }
6771 if (hintWidth < 0) {
6772 hintWidth = 0;
6773 }
6774
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006775 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006776 final boolean testDirChange = mSingleLine && mLayout != null &&
6777 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6778 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6779 int oldDir = 0;
6780 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006781 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006782 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6783 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6784 TruncateAt effectiveEllipsize = mEllipsize;
6785 if (mEllipsize == TruncateAt.MARQUEE &&
6786 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006787 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006788 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006789
Doug Feltcb3791202011-07-07 11:57:48 -07006790 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006791 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006792 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006793
Gilles Debunne287d6c62011-10-05 18:22:11 -07006794 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006795 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6796 if (switchEllipsize) {
6797 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6798 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006799 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006800 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006801 }
6802
Romain Guy4dc4f732009-06-19 15:16:40 -07006803 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006804 mHintLayout = null;
6805
6806 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006807 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006809 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006810 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006811 mHintBoring);
6812 if (hintBoring != null) {
6813 mHintBoring = hintBoring;
6814 }
6815 }
6816
6817 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006818 if (hintBoring.width <= hintWidth &&
6819 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006820 if (mSavedHintLayout != null) {
6821 mHintLayout = mSavedHintLayout.
6822 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006823 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6824 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006825 } else {
6826 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006827 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6828 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006829 }
6830
6831 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006832 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6833 if (mSavedHintLayout != null) {
6834 mHintLayout = mSavedHintLayout.
6835 replaceOrMake(mHint, mTextPaint,
6836 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6837 hintBoring, mIncludePad, mEllipsize,
6838 ellipsisWidth);
6839 } else {
6840 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6841 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6842 hintBoring, mIncludePad, mEllipsize,
6843 ellipsisWidth);
6844 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006845 }
Raph Levien39b4db72015-03-25 13:18:20 -07006846 }
6847 // TODO: code duplication with makeSingleLayout()
6848 if (mHintLayout == null) {
6849 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
Raph Levienebd66ca2015-04-30 15:27:57 -07006850 mHint.length(), mTextPaint, hintWidth)
Raph Levien39b4db72015-03-25 13:18:20 -07006851 .setAlignment(alignment)
Raph Leviena6a08282015-06-03 13:20:45 -07006852 .setTextDirection(mTextDir)
Raph Levien531c30c2015-04-30 16:29:59 -07006853 .setLineSpacing(mSpacingAdd, mSpacingMult)
Raph Levien39b4db72015-03-25 13:18:20 -07006854 .setIncludePad(mIncludePad)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006855 .setBreakStrategy(mBreakStrategy)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006856 .setHyphenationFrequency(mHyphenationFrequency)
6857 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
Raph Levien39b4db72015-03-25 13:18:20 -07006858 if (shouldEllipsize) {
6859 builder.setEllipsize(mEllipsize)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006860 .setEllipsizedWidth(ellipsisWidth);
Raph Levien39b4db72015-03-25 13:18:20 -07006861 }
6862 mHintLayout = builder.build();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006863 }
6864 }
6865
Raph Levienf5cf6c92013-04-12 11:31:31 -07006866 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006867 registerForPreDraw();
6868 }
6869
6870 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006871 if (!compressText(ellipsisWidth)) {
Chet Haase4610eef2015-12-03 07:38:11 -08006872 final int height = mLayoutParams.height;
Romain Guy939151f2009-04-08 14:22:40 -07006873 // If the size of the view does not depend on the size of the text, try to
6874 // start the marquee immediately
Chet Haase4610eef2015-12-03 07:38:11 -08006875 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006876 startMarquee();
6877 } else {
6878 // Defer the start of the marquee until we know our width (see setFrame())
6879 mRestartMarquee = true;
6880 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006881 }
6882 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006883
6884 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006885 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006886 }
6887
Selim Cinek4fb12d32015-11-19 18:10:48 -08006888 /**
6889 * @hide
6890 */
6891 protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006892 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6893 boolean useSaved) {
6894 Layout result = null;
6895 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006896 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006897 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6898 mBreakStrategy, mHyphenationFrequency,
Raph Levien39b4db72015-03-25 13:18:20 -07006899 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006900 } else {
6901 if (boring == UNKNOWN_BORING) {
6902 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6903 if (boring != null) {
6904 mBoring = boring;
6905 }
6906 }
6907
6908 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006909 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006910 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6911 if (useSaved && mSavedLayout != null) {
6912 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006913 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006914 boring, mIncludePad);
6915 } else {
6916 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006917 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006918 boring, mIncludePad);
6919 }
6920
6921 if (useSaved) {
6922 mSavedLayout = (BoringLayout) result;
6923 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006924 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006925 if (useSaved && mSavedLayout != null) {
6926 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006927 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006928 boring, mIncludePad, effectiveEllipsize,
6929 ellipsisWidth);
6930 } else {
6931 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006932 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006933 boring, mIncludePad, effectiveEllipsize,
6934 ellipsisWidth);
6935 }
Adam Powell282e3772011-08-30 16:51:11 -07006936 }
Adam Powell282e3772011-08-30 16:51:11 -07006937 }
6938 }
Raph Levien39b4db72015-03-25 13:18:20 -07006939 if (result == null) {
6940 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
Raph Levienebd66ca2015-04-30 15:27:57 -07006941 0, mTransformed.length(), mTextPaint, wantWidth)
Raph Levien39b4db72015-03-25 13:18:20 -07006942 .setAlignment(alignment)
Raph Leviena6a08282015-06-03 13:20:45 -07006943 .setTextDirection(mTextDir)
Raph Levien531c30c2015-04-30 16:29:59 -07006944 .setLineSpacing(mSpacingAdd, mSpacingMult)
Raph Levien39b4db72015-03-25 13:18:20 -07006945 .setIncludePad(mIncludePad)
Roozbeh Pournader95c7a132015-05-12 12:01:06 -07006946 .setBreakStrategy(mBreakStrategy)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006947 .setHyphenationFrequency(mHyphenationFrequency)
6948 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
Raph Levien39b4db72015-03-25 13:18:20 -07006949 if (shouldEllipsize) {
6950 builder.setEllipsize(effectiveEllipsize)
Siyamed Sinir715589f2016-02-12 18:03:55 -08006951 .setEllipsizedWidth(ellipsisWidth);
6952
Raph Levien39b4db72015-03-25 13:18:20 -07006953 }
6954 // TODO: explore always setting maxLines
6955 result = builder.build();
6956 }
Adam Powell282e3772011-08-30 16:51:11 -07006957 return result;
6958 }
6959
Romain Guy939151f2009-04-08 14:22:40 -07006960 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006961 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006962
Romain Guy3373ed62009-05-04 14:13:32 -07006963 // Only compress the text if it hasn't been compressed by the previous pass
6964 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6965 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006966 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006967 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006968 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6969 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6970 post(new Runnable() {
6971 public void run() {
6972 requestLayout();
6973 }
6974 });
6975 return true;
6976 }
6977 }
6978
6979 return false;
6980 }
6981
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006982 private static int desired(Layout layout) {
6983 int n = layout.getLineCount();
6984 CharSequence text = layout.getText();
6985 float max = 0;
6986
6987 // if any line was wrapped, we can't use it.
6988 // but it's ok for the last line not to have a newline
6989
6990 for (int i = 0; i < n - 1; i++) {
6991 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6992 return -1;
6993 }
6994
6995 for (int i = 0; i < n; i++) {
6996 max = Math.max(max, layout.getLineWidth(i));
6997 }
6998
Neil Fuller33253a42014-10-01 11:55:10 +01006999 return (int) Math.ceil(max);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007000 }
7001
7002 /**
7003 * Set whether the TextView includes extra top and bottom padding to make
7004 * room for accents that go above the normal ascent and descent.
7005 * The default is true.
7006 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007007 * @see #getIncludeFontPadding()
7008 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007009 * @attr ref android.R.styleable#TextView_includeFontPadding
7010 */
7011 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07007012 if (mIncludePad != includepad) {
7013 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007014
Gilles Debunne22378292011-08-12 10:38:52 -07007015 if (mLayout != null) {
7016 nullLayouts();
7017 requestLayout();
7018 invalidate();
7019 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007020 }
7021 }
7022
Gilles Debunnef03acef2012-04-30 19:26:19 -07007023 /**
7024 * Gets whether the TextView includes extra top and bottom padding to make
7025 * room for accents that go above the normal ascent and descent.
7026 *
7027 * @see #setIncludeFontPadding(boolean)
7028 *
7029 * @attr ref android.R.styleable#TextView_includeFontPadding
7030 */
7031 public boolean getIncludeFontPadding() {
7032 return mIncludePad;
7033 }
7034
Romain Guy4dc4f732009-06-19 15:16:40 -07007035 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007036
7037 @Override
7038 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7039 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7040 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7041 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7042 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7043
7044 int width;
7045 int height;
7046
7047 BoringLayout.Metrics boring = UNKNOWN_BORING;
7048 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
7049
Doug Feltcb3791202011-07-07 11:57:48 -07007050 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07007051 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07007052 }
7053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007054 int des = -1;
7055 boolean fromexisting = false;
7056
7057 if (widthMode == MeasureSpec.EXACTLY) {
7058 // Parent has told us how big to be. So be it.
7059 width = widthSize;
7060 } else {
7061 if (mLayout != null && mEllipsize == null) {
7062 des = desired(mLayout);
7063 }
7064
7065 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07007066 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007067 if (boring != null) {
7068 mBoring = boring;
7069 }
7070 } else {
7071 fromexisting = true;
7072 }
7073
7074 if (boring == null || boring == UNKNOWN_BORING) {
7075 if (des < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01007076 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007077 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007078 width = des;
7079 } else {
7080 width = boring.width;
7081 }
7082
7083 final Drawables dr = mDrawables;
7084 if (dr != null) {
7085 width = Math.max(width, dr.mDrawableWidthTop);
7086 width = Math.max(width, dr.mDrawableWidthBottom);
7087 }
7088
7089 if (mHint != null) {
7090 int hintDes = -1;
7091 int hintWidth;
7092
Romain Guy4dc4f732009-06-19 15:16:40 -07007093 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007094 hintDes = desired(mHintLayout);
7095 }
7096
7097 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007098 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007099 if (hintBoring != null) {
7100 mHintBoring = hintBoring;
7101 }
7102 }
7103
7104 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
7105 if (hintDes < 0) {
Neil Fuller33253a42014-10-01 11:55:10 +01007106 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007107 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007108 hintWidth = hintDes;
7109 } else {
7110 hintWidth = hintBoring.width;
7111 }
7112
7113 if (hintWidth > width) {
7114 width = hintWidth;
7115 }
7116 }
7117
7118 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
7119
7120 if (mMaxWidthMode == EMS) {
7121 width = Math.min(width, mMaxWidth * getLineHeight());
7122 } else {
7123 width = Math.min(width, mMaxWidth);
7124 }
7125
7126 if (mMinWidthMode == EMS) {
7127 width = Math.max(width, mMinWidth * getLineHeight());
7128 } else {
7129 width = Math.max(width, mMinWidth);
7130 }
7131
7132 // Check against our minimum width
7133 width = Math.max(width, getSuggestedMinimumWidth());
7134
7135 if (widthMode == MeasureSpec.AT_MOST) {
7136 width = Math.min(widthSize, width);
7137 }
7138 }
7139
7140 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
7141 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08007142
Jeff Brown033a0012011-11-11 15:30:16 -08007143 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08007144
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007145 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007146 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007147
7148 if (mLayout == null) {
7149 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07007150 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007151 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007152 final boolean layoutChanged = (mLayout.getWidth() != want) ||
7153 (hintWidth != hintWant) ||
7154 (mLayout.getEllipsizedWidth() !=
7155 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
7156
7157 final boolean widthChanged = (mHint == null) &&
7158 (mEllipsize == null) &&
7159 (want > mLayout.getWidth()) &&
7160 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
7161
7162 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
7163
7164 if (layoutChanged || maximumChanged) {
7165 if (!maximumChanged && widthChanged) {
7166 mLayout.increaseWidthTo(want);
7167 } else {
7168 makeNewLayout(want, hintWant, boring, hintBoring,
7169 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
7170 }
7171 } else {
7172 // Nothing has changed
7173 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007174 }
7175
7176 if (heightMode == MeasureSpec.EXACTLY) {
7177 // Parent has told us how big to be. So be it.
7178 height = heightSize;
7179 mDesiredHeightAtMeasure = -1;
7180 } else {
7181 int desired = getDesiredHeight();
7182
7183 height = desired;
7184 mDesiredHeightAtMeasure = desired;
7185
7186 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02007187 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007188 }
7189 }
7190
Romain Guy4dc4f732009-06-19 15:16:40 -07007191 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007192 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07007193 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007194 }
7195
7196 /*
7197 * We didn't let makeNewLayout() register to bring the cursor into view,
7198 * so do it here if there is any possibility that it is needed.
7199 */
7200 if (mMovement != null ||
7201 mLayout.getWidth() > unpaddedWidth ||
7202 mLayout.getHeight() > unpaddedHeight) {
7203 registerForPreDraw();
7204 } else {
7205 scrollTo(0, 0);
7206 }
7207
7208 setMeasuredDimension(width, height);
7209 }
7210
7211 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007212 return Math.max(
7213 getDesiredHeight(mLayout, true),
7214 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007215 }
7216
7217 private int getDesiredHeight(Layout layout, boolean cap) {
7218 if (layout == null) {
7219 return 0;
7220 }
7221
7222 int linecount = layout.getLineCount();
7223 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7224 int desired = layout.getLineTop(linecount);
7225
7226 final Drawables dr = mDrawables;
7227 if (dr != null) {
7228 desired = Math.max(desired, dr.mDrawableHeightLeft);
7229 desired = Math.max(desired, dr.mDrawableHeightRight);
7230 }
7231
7232 desired += pad;
7233
7234 if (mMaxMode == LINES) {
7235 /*
7236 * Don't cap the hint to a certain number of lines.
7237 * (Do cap it, though, if we have a maximum pixel height.)
7238 */
7239 if (cap) {
7240 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08007241 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007242
7243 if (dr != null) {
7244 desired = Math.max(desired, dr.mDrawableHeightLeft);
7245 desired = Math.max(desired, dr.mDrawableHeightRight);
7246 }
7247
7248 desired += pad;
7249 linecount = mMaximum;
7250 }
7251 }
7252 } else {
7253 desired = Math.min(desired, mMaximum);
7254 }
7255
7256 if (mMinMode == LINES) {
7257 if (linecount < mMinimum) {
7258 desired += getLineHeight() * (mMinimum - linecount);
7259 }
7260 } else {
7261 desired = Math.max(desired, mMinimum);
7262 }
7263
7264 // Check against our minimum height
7265 desired = Math.max(desired, getSuggestedMinimumHeight());
7266
7267 return desired;
7268 }
7269
7270 /**
7271 * Check whether a change to the existing text layout requires a
7272 * new view layout.
7273 */
7274 private void checkForResize() {
Chet Haase4610eef2015-12-03 07:38:11 -08007275 boolean sizeChanged = false;
7276
7277 if (mLayout != null) {
7278 // Check if our width changed
7279 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7280 sizeChanged = true;
7281 invalidate();
7282 }
7283
7284 // Check if our height changed
7285 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7286 int desiredHeight = getDesiredHeight();
7287
7288 if (desiredHeight != this.getHeight()) {
7289 sizeChanged = true;
7290 }
7291 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7292 if (mDesiredHeightAtMeasure >= 0) {
7293 int desiredHeight = getDesiredHeight();
7294
7295 if (desiredHeight != mDesiredHeightAtMeasure) {
7296 sizeChanged = true;
7297 }
7298 }
7299 }
7300 }
7301
7302 if (sizeChanged) {
7303 requestLayout();
7304 // caller will have already invalidated
7305 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007306 }
7307
7308 /**
7309 * Check whether entirely new text requires a new view layout
7310 * or merely a new text layout.
7311 */
7312 private void checkForRelayout() {
Chet Haase4610eef2015-12-03 07:38:11 -08007313 // If we have a fixed width, we can just swap in a new text layout
7314 // if the text height stays the same or if the view height is fixed.
7315
7316 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7317 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7318 (mHint == null || mHintLayout != null) &&
7319 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7320 // Static width, so try making a new text layout.
7321
7322 int oldht = mLayout.getHeight();
7323 int want = mLayout.getWidth();
7324 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7325
7326 /*
7327 * No need to bring the text into view, since the size is not
7328 * changing (unless we do the requestLayout(), in which case it
7329 * will happen at measure).
7330 */
7331 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7332 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7333 false);
7334
7335 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7336 // In a fixed-height view, so use our new text layout.
7337 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7338 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7339 invalidate();
7340 return;
7341 }
7342
7343 // Dynamic height, but height has stayed the same,
7344 // so use our new text layout.
7345 if (mLayout.getHeight() == oldht &&
7346 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7347 invalidate();
7348 return;
7349 }
7350 }
7351
7352 // We lose: the height has changed and we have a dynamic height.
7353 // Request a new view layout using our new text layout.
7354 requestLayout();
7355 invalidate();
7356 } else {
7357 // Dynamic width, so we have no choice but to request a new
7358 // view layout with a new text layout.
7359 nullLayouts();
7360 requestLayout();
7361 invalidate();
7362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007363 }
7364
Gilles Debunne954325e2012-01-25 11:57:06 -08007365 @Override
7366 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7367 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07007368 if (mDeferScroll >= 0) {
7369 int curs = mDeferScroll;
7370 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07007371 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07007372 }
Gilles Debunne954325e2012-01-25 11:57:06 -08007373 }
7374
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007375 private boolean isShowingHint() {
7376 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7377 }
7378
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007379 /**
7380 * Returns true if anything changed.
7381 */
7382 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007383 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007384 int line = 0;
7385 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007386 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007387 }
7388
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007389 Layout.Alignment a = layout.getParagraphAlignment(line);
7390 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007391 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7392 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007393 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007394
7395 int scrollx, scrolly;
7396
Doug Felt25b9f422011-07-11 13:48:37 -07007397 // Convert to left, center, or right alignment.
7398 if (a == Layout.Alignment.ALIGN_NORMAL) {
7399 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7400 Layout.Alignment.ALIGN_RIGHT;
7401 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7402 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7403 Layout.Alignment.ALIGN_LEFT;
7404 }
7405
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007406 if (a == Layout.Alignment.ALIGN_CENTER) {
7407 /*
7408 * Keep centered if possible, or, if it is too wide to fit,
7409 * keep leading edge in view.
7410 */
7411
Neil Fuller33253a42014-10-01 11:55:10 +01007412 int left = (int) Math.floor(layout.getLineLeft(line));
7413 int right = (int) Math.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007414
7415 if (right - left < hspace) {
7416 scrollx = (right + left) / 2 - hspace / 2;
7417 } else {
7418 if (dir < 0) {
7419 scrollx = right - hspace;
7420 } else {
7421 scrollx = left;
7422 }
7423 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007424 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Neil Fuller33253a42014-10-01 11:55:10 +01007425 int right = (int) Math.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07007426 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07007427 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Neil Fuller33253a42014-10-01 11:55:10 +01007428 scrollx = (int) Math.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007429 }
7430
7431 if (ht < vspace) {
7432 scrolly = 0;
7433 } else {
7434 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7435 scrolly = ht - vspace;
7436 } else {
7437 scrolly = 0;
7438 }
7439 }
7440
7441 if (scrollx != mScrollX || scrolly != mScrollY) {
7442 scrollTo(scrollx, scrolly);
7443 return true;
7444 } else {
7445 return false;
7446 }
7447 }
7448
7449 /**
7450 * Move the point, specified by the offset, into the view if it is needed.
7451 * This has to be called after layout. Returns true if anything changed.
7452 */
7453 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07007454 if (isLayoutRequested()) {
7455 mDeferScroll = offset;
7456 return false;
7457 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007458 boolean changed = false;
7459
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007460 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07007461
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007462 if (layout == null) return changed;
7463
7464 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007465
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007466 int grav;
7467
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007468 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07007469 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007470 grav = 1;
7471 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007472 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007473 grav = -1;
7474 break;
Doug Felt25b9f422011-07-11 13:48:37 -07007475 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007476 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007477 break;
7478 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08007479 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07007480 break;
7481 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007482 default:
7483 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07007484 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007485 }
7486
Raph Levienafe8e9b2012-12-19 16:09:32 -08007487 // We only want to clamp the cursor to fit within the layout width
7488 // in left-to-right modes, because in a right to left alignment,
7489 // we want to scroll to keep the line-right on the screen, as other
7490 // lines are likely to have text flush with the right margin, which
7491 // we want to keep visible.
7492 // A better long-term solution would probably be to measure both
7493 // the full line and a blank-trimmed version, and, for example, use
7494 // the latter measurement for centering and right alignment, but for
7495 // the time being we only implement the cursor clamping in left to
7496 // right where it is most likely to be annoying.
7497 final boolean clamped = grav > 0;
7498 // FIXME: Is it okay to truncate this, or should we round?
7499 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7500 final int top = layout.getLineTop(line);
7501 final int bottom = layout.getLineTop(line + 1);
7502
Neil Fuller33253a42014-10-01 11:55:10 +01007503 int left = (int) Math.floor(layout.getLineLeft(line));
7504 int right = (int) Math.ceil(layout.getLineRight(line));
Raph Levienafe8e9b2012-12-19 16:09:32 -08007505 int ht = layout.getHeight();
7506
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007507 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7508 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08007509 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7510 // If cursor has been clamped, make sure we don't scroll.
7511 right = Math.max(x, left + hspace);
7512 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007513
7514 int hslack = (bottom - top) / 2;
7515 int vslack = hslack;
7516
7517 if (vslack > vspace / 4)
7518 vslack = vspace / 4;
7519 if (hslack > hspace / 4)
7520 hslack = hspace / 4;
7521
7522 int hs = mScrollX;
7523 int vs = mScrollY;
7524
7525 if (top - vs < vslack)
7526 vs = top - vslack;
7527 if (bottom - vs > vspace - vslack)
7528 vs = bottom - (vspace - vslack);
7529 if (ht - vs < vspace)
7530 vs = ht - vspace;
7531 if (0 - vs > 0)
7532 vs = 0;
7533
7534 if (grav != 0) {
7535 if (x - hs < hslack) {
7536 hs = x - hslack;
7537 }
7538 if (x - hs > hspace - hslack) {
7539 hs = x - (hspace - hslack);
7540 }
7541 }
7542
7543 if (grav < 0) {
7544 if (left - hs > 0)
7545 hs = left;
7546 if (right - hs < hspace)
7547 hs = right - hspace;
7548 } else if (grav > 0) {
7549 if (right - hs < hspace)
7550 hs = right - hspace;
7551 if (left - hs > 0)
7552 hs = left;
7553 } else /* grav == 0 */ {
7554 if (right - left <= hspace) {
7555 /*
7556 * If the entire text fits, center it exactly.
7557 */
7558 hs = left - (hspace - (right - left)) / 2;
7559 } else if (x > right - hslack) {
7560 /*
7561 * If we are near the right edge, keep the right edge
7562 * at the edge of the view.
7563 */
7564 hs = right - hspace;
7565 } else if (x < left + hslack) {
7566 /*
7567 * If we are near the left edge, keep the left edge
7568 * at the edge of the view.
7569 */
7570 hs = left;
7571 } else if (left > hs) {
7572 /*
7573 * Is there whitespace visible at the left? Fix it if so.
7574 */
7575 hs = left;
7576 } else if (right < hs + hspace) {
7577 /*
7578 * Is there whitespace visible at the right? Fix it if so.
7579 */
7580 hs = right - hspace;
7581 } else {
7582 /*
7583 * Otherwise, float as needed.
7584 */
7585 if (x - hs < hslack) {
7586 hs = x - hslack;
7587 }
7588 if (x - hs > hspace - hslack) {
7589 hs = x - (hspace - hslack);
7590 }
7591 }
7592 }
7593
7594 if (hs != mScrollX || vs != mScrollY) {
7595 if (mScroller == null) {
7596 scrollTo(hs, vs);
7597 } else {
7598 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7599 int dx = hs - mScrollX;
7600 int dy = vs - mScrollY;
7601
7602 if (duration > ANIMATED_SCROLL_GAP) {
7603 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07007604 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007605 invalidate();
7606 } else {
7607 if (!mScroller.isFinished()) {
7608 mScroller.abortAnimation();
7609 }
7610
7611 scrollBy(dx, dy);
7612 }
7613
7614 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7615 }
7616
7617 changed = true;
7618 }
7619
7620 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08007621 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7622 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007623
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007624 // The offsets here are to ensure the rectangle we are using is
7625 // within our view bounds, in case the cursor is on the far left
7626 // or right. If it isn't withing the bounds, then this request
7627 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08007628 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07007629 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08007630 getInterestingRect(mTempRect, line);
7631 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007632
Gilles Debunne716dbf62011-03-07 18:12:10 -08007633 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007634 changed = true;
7635 }
7636 }
7637
7638 return changed;
7639 }
7640
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007641 /**
7642 * Move the cursor, if needed, so that it is at an offset that is visible
7643 * to the user. This will not move the cursor if it represents more than
7644 * one character (a selection range). This will only work if the
7645 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007646 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07007647 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007648 */
7649 public boolean moveCursorToVisibleOffset() {
7650 if (!(mText instanceof Spannable)) {
7651 return false;
7652 }
Gilles Debunne05336272010-07-09 20:13:45 -07007653 int start = getSelectionStart();
7654 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007655 if (start != end) {
7656 return false;
7657 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007658
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007659 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007660
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007661 int line = mLayout.getLineForOffset(start);
7662
7663 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007664 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007665 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7666 int vslack = (bottom - top) / 2;
7667 if (vslack > vspace / 4)
7668 vslack = vspace / 4;
7669 final int vs = mScrollY;
7670
7671 if (top < (vs+vslack)) {
7672 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7673 } else if (bottom > (vspace+vs-vslack)) {
7674 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7675 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007676
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007677 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07007678
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007679 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7680 final int hs = mScrollX;
7681 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7682 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07007683
Doug Feltc982f602010-05-25 11:51:40 -07007684 // line might contain bidirectional text
7685 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7686 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7687
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007688 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07007689 if (newStart < lowChar) {
7690 newStart = lowChar;
7691 } else if (newStart > highChar) {
7692 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007693 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007694
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007695 if (newStart != start) {
7696 Selection.setSelection((Spannable)mText, newStart);
7697 return true;
7698 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007699
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07007700 return false;
7701 }
7702
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007703 @Override
7704 public void computeScroll() {
7705 if (mScroller != null) {
7706 if (mScroller.computeScrollOffset()) {
7707 mScrollX = mScroller.getCurrX();
7708 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08007709 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007710 postInvalidate(); // So we draw again
7711 }
7712 }
7713 }
7714
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007715 private void getInterestingRect(Rect r, int line) {
7716 convertFromViewportToContentCoordinates(r);
7717
7718 // Rectangle can can be expanded on first and last line to take
7719 // padding into account.
7720 // TODO Take left/right padding into account too?
7721 if (line == 0) r.top -= getExtendedPaddingTop();
7722 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7723 }
7724
7725 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007726 final int horizontalOffset = viewportToContentHorizontalOffset();
7727 r.left += horizontalOffset;
7728 r.right += horizontalOffset;
7729
7730 final int verticalOffset = viewportToContentVerticalOffset();
7731 r.top += verticalOffset;
7732 r.bottom += verticalOffset;
7733 }
7734
Gilles Debunned88876a2012-03-16 17:34:04 -07007735 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007736 return getCompoundPaddingLeft() - mScrollX;
7737 }
7738
Gilles Debunned88876a2012-03-16 17:34:04 -07007739 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007740 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007741 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007742 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007743 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07007744 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007745 }
7746
7747 @Override
7748 public void debug(int depth) {
7749 super.debug(depth);
7750
7751 String output = debugIndent(depth);
7752 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7753 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7754 + "} ";
7755
7756 if (mText != null) {
7757
7758 output += "mText=\"" + mText + "\" ";
7759 if (mLayout != null) {
7760 output += "mLayout width=" + mLayout.getWidth()
7761 + " height=" + mLayout.getHeight();
7762 }
7763 } else {
7764 output += "mText=NULL";
7765 }
7766 Log.d(VIEW_LOG_TAG, output);
7767 }
7768
7769 /**
7770 * Convenience for {@link Selection#getSelectionStart}.
7771 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007772 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007773 public int getSelectionStart() {
7774 return Selection.getSelectionStart(getText());
7775 }
7776
7777 /**
7778 * Convenience for {@link Selection#getSelectionEnd}.
7779 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007780 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007781 public int getSelectionEnd() {
7782 return Selection.getSelectionEnd(getText());
7783 }
7784
7785 /**
7786 * Return true iff there is a selection inside this text view.
7787 */
7788 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007789 final int selectionStart = getSelectionStart();
7790 final int selectionEnd = getSelectionEnd();
7791
7792 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007793 }
7794
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007795 String getSelectedText() {
Seigo Nonaka86d60cd2015-06-25 19:02:17 +09007796 if (!hasSelection()) {
7797 return null;
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007798 }
Seigo Nonaka86d60cd2015-06-25 19:02:17 +09007799
7800 final int start = getSelectionStart();
7801 final int end = getSelectionEnd();
7802 return String.valueOf(
7803 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00007804 }
7805
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007806 /**
7807 * Sets the properties of this field (lines, horizontally scrolling,
7808 * transformation method) to be for a single-line input.
7809 *
7810 * @attr ref android.R.styleable#TextView_singleLine
7811 */
7812 public void setSingleLine() {
7813 setSingleLine(true);
7814 }
7815
7816 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007817 * Sets the properties of this field to transform input to ALL CAPS
7818 * display. This may use a "small caps" formatting if available.
7819 * This setting will be ignored if this field is editable or selectable.
7820 *
7821 * This call replaces the current transformation method. Disabling this
7822 * will not necessarily restore the previous behavior from before this
7823 * was enabled.
7824 *
7825 * @see #setTransformationMethod(TransformationMethod)
7826 * @attr ref android.R.styleable#TextView_textAllCaps
7827 */
7828 public void setAllCaps(boolean allCaps) {
7829 if (allCaps) {
7830 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7831 } else {
7832 setTransformationMethod(null);
7833 }
7834 }
7835
7836 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007837 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7838 * transformation method) to be for a single-line input; if false, restores these to the default
7839 * conditions.
7840 *
7841 * Note that the default conditions are not necessarily those that were in effect prior this
7842 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007843 *
7844 * @attr ref android.R.styleable#TextView_singleLine
7845 */
7846 @android.view.RemotableViewMethod
7847 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007848 // Could be used, but may break backward compatibility.
7849 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007850 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007851 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007852 }
7853
7854 /**
7855 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7856 * @param singleLine
7857 */
7858 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007859 if (mEditor != null &&
7860 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007861 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007862 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007863 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007864 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007865 }
7866 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007867 }
7868
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007869 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7870 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007871 mSingleLine = singleLine;
7872 if (singleLine) {
7873 setLines(1);
7874 setHorizontallyScrolling(true);
7875 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007876 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007877 }
7878 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007879 if (changeMaxLines) {
7880 setMaxLines(Integer.MAX_VALUE);
7881 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007882 setHorizontallyScrolling(false);
7883 if (applyTransformation) {
7884 setTransformationMethod(null);
7885 }
7886 }
7887 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007888
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007889 /**
7890 * Causes words in the text that are longer than the view is wide
7891 * to be ellipsized instead of broken in the middle. You may also
7892 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007893 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007894 * to turn off ellipsizing.
7895 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007896 * If {@link #setMaxLines} has been used to set two or more lines,
Newton Allen8f8a11b2013-11-26 10:25:38 -08007897 * only {@link android.text.TextUtils.TruncateAt#END} and
7898 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
Gilles Debunne6435a562011-08-04 21:22:30 -07007899 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007900 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007901 * @attr ref android.R.styleable#TextView_ellipsize
7902 */
7903 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007904 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7905 if (mEllipsize != where) {
7906 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007907
Gilles Debunne22378292011-08-12 10:38:52 -07007908 if (mLayout != null) {
7909 nullLayouts();
7910 requestLayout();
7911 invalidate();
7912 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007913 }
7914 }
7915
7916 /**
7917 * Sets how many times to repeat the marquee animation. Only applied if the
7918 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7919 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007920 * @see #getMarqueeRepeatLimit()
7921 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007922 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7923 */
7924 public void setMarqueeRepeatLimit(int marqueeLimit) {
7925 mMarqueeRepeatLimit = marqueeLimit;
7926 }
7927
7928 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007929 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7930 * TextView has marquee enabled.
7931 *
7932 * @return the number of times the marquee animation is repeated. -1 if the animation
7933 * repeats indefinitely
7934 *
7935 * @see #setMarqueeRepeatLimit(int)
7936 *
7937 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7938 */
7939 public int getMarqueeRepeatLimit() {
7940 return mMarqueeRepeatLimit;
7941 }
7942
7943 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007944 * Returns where, if anywhere, words that are longer than the view
7945 * is wide should be ellipsized.
7946 */
7947 @ViewDebug.ExportedProperty
7948 public TextUtils.TruncateAt getEllipsize() {
7949 return mEllipsize;
7950 }
7951
7952 /**
7953 * Set the TextView so that when it takes focus, all the text is
7954 * selected.
7955 *
7956 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7957 */
7958 @android.view.RemotableViewMethod
7959 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007960 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007961 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007962
7963 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7964 setText(mText, BufferType.SPANNABLE);
7965 }
7966 }
7967
7968 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007969 * Set whether the cursor is visible. The default is true. Note that this property only
7970 * makes sense for editable TextView.
7971 *
7972 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007973 *
7974 * @attr ref android.R.styleable#TextView_cursorVisible
7975 */
7976 @android.view.RemotableViewMethod
7977 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007978 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007979 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007980 if (mEditor.mCursorVisible != visible) {
7981 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007982 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007983
Gilles Debunne2d373a12012-04-20 15:32:19 -07007984 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007985
Gilles Debunne3d010062011-02-18 14:16:41 -08007986 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007987 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007988 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007989 }
7990
Gilles Debunnef03acef2012-04-30 19:26:19 -07007991 /**
7992 * @return whether or not the cursor is visible (assuming this TextView is editable)
7993 *
7994 * @see #setCursorVisible(boolean)
7995 *
7996 * @attr ref android.R.styleable#TextView_cursorVisible
7997 */
7998 public boolean isCursorVisible() {
7999 // true is the default value
8000 return mEditor == null ? true : mEditor.mCursorVisible;
8001 }
8002
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008003 private boolean canMarquee() {
8004 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07008005 return width > 0 && (mLayout.getLineWidth(0) > width ||
8006 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
8007 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008008 }
8009
8010 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07008011 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08008012 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07008013
Romain Guy939151f2009-04-08 14:22:40 -07008014 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
8015 return;
8016 }
8017
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008018 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
8019 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07008020
Adam Powell282e3772011-08-30 16:51:11 -07008021 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8022 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
8023 final Layout tmp = mLayout;
8024 mLayout = mSavedMarqueeModeLayout;
8025 mSavedMarqueeModeLayout = tmp;
8026 setHorizontalFadingEdgeEnabled(true);
8027 requestLayout();
8028 invalidate();
8029 }
8030
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008031 if (mMarquee == null) mMarquee = new Marquee(this);
8032 mMarquee.start(mMarqueeRepeatLimit);
8033 }
8034 }
8035
8036 private void stopMarquee() {
8037 if (mMarquee != null && !mMarquee.isStopped()) {
8038 mMarquee.stop();
8039 }
Adam Powell282e3772011-08-30 16:51:11 -07008040
8041 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
8042 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
8043 final Layout tmp = mSavedMarqueeModeLayout;
8044 mSavedMarqueeModeLayout = mLayout;
8045 mLayout = tmp;
8046 setHorizontalFadingEdgeEnabled(false);
8047 requestLayout();
8048 invalidate();
8049 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008050 }
8051
8052 private void startStopMarquee(boolean start) {
8053 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8054 if (start) {
8055 startMarquee();
8056 } else {
8057 stopMarquee();
8058 }
8059 }
8060 }
8061
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008062 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08008063 * This method is called when the text is changed, in case any subclasses
8064 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008065 *
Gilles Debunne4469e602011-03-09 14:38:04 -08008066 * Within <code>text</code>, the <code>lengthAfter</code> characters
8067 * beginning at <code>start</code> have just replaced old text that had
8068 * length <code>lengthBefore</code>. It is an error to attempt to make
8069 * changes to <code>text</code> from this callback.
8070 *
8071 * @param text The text the TextView is displaying
8072 * @param start The offset of the start of the range of the text that was
8073 * modified
8074 * @param lengthBefore The length of the former text that has been replaced
8075 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008076 */
Gilles Debunne4469e602011-03-09 14:38:04 -08008077 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07008078 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008079 }
8080
8081 /**
8082 * This method is called when the selection has changed, in case any
8083 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008084 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008085 * @param selStart The new selection start location.
8086 * @param selEnd The new selection end location.
8087 */
8088 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07008089 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008090 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07008091
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008092 /**
8093 * Adds a TextWatcher to the list of those whose methods are called
8094 * whenever this TextView's text changes.
8095 * <p>
8096 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
8097 * not called after {@link #setText} calls. Now, doing {@link #setText}
8098 * if there are any text changed listeners forces the buffer type to
8099 * Editable if it would not otherwise be and does call this method.
8100 */
8101 public void addTextChangedListener(TextWatcher watcher) {
8102 if (mListeners == null) {
8103 mListeners = new ArrayList<TextWatcher>();
8104 }
8105
8106 mListeners.add(watcher);
8107 }
8108
8109 /**
8110 * Removes the specified TextWatcher from the list of those whose
8111 * methods are called
8112 * whenever this TextView's text changes.
8113 */
8114 public void removeTextChangedListener(TextWatcher watcher) {
8115 if (mListeners != null) {
8116 int i = mListeners.indexOf(watcher);
8117
8118 if (i >= 0) {
8119 mListeners.remove(i);
8120 }
8121 }
8122 }
8123
Gilles Debunne6435a562011-08-04 21:22:30 -07008124 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008125 if (mListeners != null) {
8126 final ArrayList<TextWatcher> list = mListeners;
8127 final int count = list.size();
8128 for (int i = 0; i < count; i++) {
8129 list.get(i).beforeTextChanged(text, start, before, after);
8130 }
8131 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008132
8133 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008134 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
8135 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07008136 }
8137
8138 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008139 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07008140 if (!(mText instanceof Editable)) return;
8141 Editable text = (Editable) mText;
8142
8143 T[] spans = text.getSpans(start, end, type);
8144 final int length = spans.length;
8145 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008146 final int spanStart = text.getSpanStart(spans[i]);
8147 final int spanEnd = text.getSpanEnd(spans[i]);
8148 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07008149 text.removeSpan(spans[i]);
8150 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008151 }
8152
Satoshi Kataokad7429c12013-06-05 16:30:23 +09008153 void removeAdjacentSuggestionSpans(final int pos) {
8154 if (!(mText instanceof Editable)) return;
8155 final Editable text = (Editable) mText;
8156
8157 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
8158 final int length = spans.length;
8159 for (int i = 0; i < length; i++) {
8160 final int spanStart = text.getSpanStart(spans[i]);
8161 final int spanEnd = text.getSpanEnd(spans[i]);
8162 if (spanEnd == pos || spanStart == pos) {
8163 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
8164 text.removeSpan(spans[i]);
8165 }
8166 }
8167 }
8168 }
8169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008170 /**
8171 * Not private so it can be called from an inner class without going
8172 * through a thunk.
8173 */
Gilles Debunne6435a562011-08-04 21:22:30 -07008174 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008175 if (mListeners != null) {
8176 final ArrayList<TextWatcher> list = mListeners;
8177 final int count = list.size();
8178 for (int i = 0; i < count; i++) {
8179 list.get(i).onTextChanged(text, start, before, after);
8180 }
8181 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01008182
Gilles Debunne2d373a12012-04-20 15:32:19 -07008183 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008184 }
8185
8186 /**
8187 * Not private so it can be called from an inner class without going
8188 * through a thunk.
8189 */
8190 void sendAfterTextChanged(Editable text) {
8191 if (mListeners != null) {
8192 final ArrayList<TextWatcher> list = mListeners;
8193 final int count = list.size();
8194 for (int i = 0; i < count; i++) {
8195 list.get(i).afterTextChanged(text);
8196 }
8197 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07008198 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008199 }
8200
Gilles Debunned88876a2012-03-16 17:34:04 -07008201 void updateAfterEdit() {
8202 invalidate();
8203 int curs = getSelectionStart();
8204
8205 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8206 registerForPreDraw();
8207 }
8208
Raph Levienf5c1a872012-10-15 17:22:26 -07008209 checkForResize();
8210
Gilles Debunned88876a2012-03-16 17:34:04 -07008211 if (curs >= 0) {
8212 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008213 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07008214 bringPointIntoView(curs);
8215 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008216 }
8217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008218 /**
8219 * Not private so it can be called from an inner class without going
8220 * through a thunk.
8221 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008222 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Andrei Stingaceanu373816e2015-05-28 11:26:28 +01008223 sLastCutCopyOrTextChangedTime = 0;
8224
Gilles Debunne2d373a12012-04-20 15:32:19 -07008225 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008226 if (ims == null || ims.mBatchEditNesting == 0) {
8227 updateAfterEdit();
8228 }
8229 if (ims != null) {
8230 ims.mContentChanged = true;
8231 if (ims.mChangedStart < 0) {
8232 ims.mChangedStart = start;
8233 ims.mChangedEnd = start+before;
8234 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01008235 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8236 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008237 }
8238 ims.mChangedDelta += after-before;
8239 }
Yigit Boyar412bb5c2014-08-22 16:02:40 -07008240 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008241 sendOnTextChanged(buffer, start, before, after);
8242 onTextChanged(buffer, start, before, after);
8243 }
Gilles Debunne60e21862012-01-30 15:04:14 -08008244
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008245 /**
8246 * Not private so it can be called from an inner class without going
8247 * through a thunk.
8248 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08008249 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008250 // XXX Make the start and end move together if this ends up
8251 // spending too much time invalidating.
8252
8253 boolean selChanged = false;
8254 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008255
Gilles Debunne2d373a12012-04-20 15:32:19 -07008256 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008257
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008258 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008259 selChanged = true;
8260 newSelEnd = newStart;
8261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008262 if (oldStart >= 0 || newStart >= 0) {
8263 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07008264 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008265 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008266 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008267 }
8268 }
8269
8270 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008271 selChanged = true;
8272 newSelStart = newStart;
8273
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008274 if (oldStart >= 0 || newStart >= 0) {
8275 int end = Selection.getSelectionEnd(buf);
8276 invalidateCursor(end, oldStart, newStart);
8277 }
8278 }
8279
8280 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008281 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008282 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08008283
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008284 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8285 if (newSelStart < 0) {
8286 newSelStart = Selection.getSelectionStart(buf);
8287 }
8288 if (newSelEnd < 0) {
8289 newSelEnd = Selection.getSelectionEnd(buf);
8290 }
8291 onSelectionChanged(newSelStart, newSelEnd);
8292 }
8293 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08008294
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08008295 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8296 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008297 if (ims == null || ims.mBatchEditNesting == 0) {
8298 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08008299 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008300 checkForResize();
8301 } else {
8302 ims.mContentChanged = true;
8303 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07008304 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008305 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8306 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07008307 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008308 }
8309
8310 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08008311 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008312 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8313 ims.mSelectionModeChanged = true;
8314 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008315
8316 if (Selection.getSelectionStart(buf) >= 0) {
8317 if (ims == null || ims.mBatchEditNesting == 0) {
8318 invalidateCursor();
8319 } else {
8320 ims.mCursorChanged = true;
8321 }
8322 }
8323 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008324
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008325 if (what instanceof ParcelableSpan) {
8326 // If this is a span that can be sent to a remote process,
8327 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07008328 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008329 if (ims.mBatchEditNesting != 0) {
8330 if (oldStart >= 0) {
8331 if (ims.mChangedStart > oldStart) {
8332 ims.mChangedStart = oldStart;
8333 }
8334 if (ims.mChangedStart > oldEnd) {
8335 ims.mChangedStart = oldEnd;
8336 }
8337 }
8338 if (newStart >= 0) {
8339 if (ims.mChangedStart > newStart) {
8340 ims.mChangedStart = newStart;
8341 }
8342 if (ims.mChangedStart > newEnd) {
8343 ims.mChangedStart = newEnd;
8344 }
8345 }
8346 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008347 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008348 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07008349 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008350 ims.mContentChanged = true;
8351 }
8352 }
8353 }
Gilles Debunne6435a562011-08-04 21:22:30 -07008354
Gilles Debunne2d373a12012-04-20 15:32:19 -07008355 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8356 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07008357 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07008358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008359 }
8360
Gilles Debunne6435a562011-08-04 21:22:30 -07008361 /**
Romain Guydcc490f2010-02-24 17:59:35 -08008362 * @hide
8363 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008364 @Override
Romain Guya440b002010-02-24 15:57:54 -08008365 public void dispatchFinishTemporaryDetach() {
8366 mDispatchTemporaryDetach = true;
8367 super.dispatchFinishTemporaryDetach();
8368 mDispatchTemporaryDetach = false;
8369 }
8370
8371 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008372 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008373 super.onStartTemporaryDetach();
8374 // Only track when onStartTemporaryDetach() is called directly,
8375 // usually because this instance is an editable field in a list
8376 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08008377
Adam Powell057a5852012-05-11 10:28:38 -07008378 // Tell the editor that we are temporarily detached. It can use this to preserve
8379 // selection state as needed.
8380 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008381 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008383 @Override
8384 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08008385 super.onFinishTemporaryDetach();
8386 // Only track when onStartTemporaryDetach() is called directly,
8387 // usually because this instance is an editable field in a list
8388 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07008389 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008390 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07008391
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008392 @Override
8393 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8394 if (mTemporaryDetach) {
8395 // If we are temporarily in the detach state, then do nothing.
8396 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8397 return;
8398 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008399
Gilles Debunne2d373a12012-04-20 15:32:19 -07008400 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07008401
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008402 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008403 if (mText instanceof Spannable) {
8404 Spannable sp = (Spannable) mText;
8405 MetaKeyKeyListener.resetMetaState(sp);
8406 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008407 }
8408
8409 startStopMarquee(focused);
8410
8411 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008412 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008413 }
8414
8415 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8416 }
8417
8418 @Override
8419 public void onWindowFocusChanged(boolean hasWindowFocus) {
8420 super.onWindowFocusChanged(hasWindowFocus);
8421
Gilles Debunne2d373a12012-04-20 15:32:19 -07008422 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008423
8424 startStopMarquee(hasWindowFocus);
8425 }
8426
Adam Powellba0a2c32010-09-28 17:41:23 -07008427 @Override
8428 protected void onVisibilityChanged(View changedView, int visibility) {
8429 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08008430 if (mEditor != null && visibility != VISIBLE) {
Mady Mellora2861452015-06-25 08:40:27 -07008431 mEditor.hideCursorAndSpanControllers();
Clara Bayarri7938cdb2015-06-02 20:03:45 +01008432 stopTextActionMode();
Adam Powellba0a2c32010-09-28 17:41:23 -07008433 }
8434 }
8435
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008436 /**
8437 * Use {@link BaseInputConnection#removeComposingSpans
8438 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8439 * state from this text view.
8440 */
8441 public void clearComposingText() {
8442 if (mText instanceof Spannable) {
8443 BaseInputConnection.removeComposingSpans((Spannable)mText);
8444 }
8445 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008446
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008447 @Override
8448 public void setSelected(boolean selected) {
8449 boolean wasSelected = isSelected();
8450
8451 super.setSelected(selected);
8452
8453 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8454 if (selected) {
8455 startMarquee();
8456 } else {
8457 stopMarquee();
8458 }
8459 }
8460 }
8461
8462 @Override
8463 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008464 final int action = event.getActionMasked();
Mady Mellor2ff2cd82015-03-02 10:37:01 -08008465 if (mEditor != null) {
8466 mEditor.onTouchEvent(event);
8467
8468 if (mEditor.mSelectionModifierCursorController != null &&
8469 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8470 return true;
8471 }
8472 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008473
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008474 final boolean superResult = super.onTouchEvent(event);
8475
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008476 /*
Clara Bayarrib71dddd2015-06-04 23:17:30 +01008477 * Don't handle the release after a long press, because it will move the selection away from
8478 * whatever the menu action was trying to affect. If the long press should have triggered an
8479 * insertion action mode, we can now actually show it.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008480 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07008481 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8482 mEditor.mDiscardNextActionUp = false;
Clara Bayarrib71dddd2015-06-04 23:17:30 +01008483
8484 if (mEditor.mIsInsertionActionModeStartPending) {
8485 mEditor.startInsertionActionMode();
8486 mEditor.mIsInsertionActionModeStartPending = false;
8487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008488 return superResult;
8489 }
8490
Gilles Debunne70a63122011-09-01 13:27:33 -07008491 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008492 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08008493
Gilles Debunne70a63122011-09-01 13:27:33 -07008494 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03008495 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008496 boolean handled = false;
8497
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07008498 if (mMovement != null) {
8499 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8500 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008501
Gilles Debunne60e21862012-01-30 15:04:14 -08008502 final boolean textIsSelectable = isTextSelectable();
8503 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008504 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07008505 // on non editable text that support text selection.
8506 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008507 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8508 getSelectionEnd(), ClickableSpan.class);
8509
Gilles Debunne822b8f02012-01-17 18:02:15 -08008510 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08008511 links[0].onClick(this);
8512 handled = true;
8513 }
8514 }
8515
Gilles Debunne60e21862012-01-30 15:04:14 -08008516 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008517 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09008518 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09008519 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07008520 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08008521 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07008522 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07008523
Gilles Debunned88876a2012-03-16 17:34:04 -07008524 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07008525 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07008526
8527 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008528 }
8529
The Android Open Source Project4df24232009-03-05 14:34:35 -08008530 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008531 return true;
8532 }
8533 }
8534
8535 return superResult;
8536 }
8537
Jeff Brown8f345672011-02-26 13:29:53 -08008538 @Override
8539 public boolean onGenericMotionEvent(MotionEvent event) {
8540 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8541 try {
8542 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8543 return true;
8544 }
8545 } catch (AbstractMethodError ex) {
8546 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8547 // Ignore its absence in case third party applications implemented the
8548 // interface directly.
8549 }
8550 }
8551 return super.onGenericMotionEvent(event);
8552 }
8553
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09008554 @Override
8555 protected void onCreateContextMenu(ContextMenu menu) {
8556 if (mEditor != null) {
8557 mEditor.onCreateContextMenu(menu);
8558 }
8559 }
8560
8561 @Override
8562 public boolean showContextMenu() {
8563 if (mEditor != null) {
8564 mEditor.setContextMenuAnchor(Float.NaN, Float.NaN);
8565 }
8566 return super.showContextMenu();
8567 }
8568
8569 @Override
8570 public boolean showContextMenu(float x, float y) {
8571 if (mEditor != null) {
8572 mEditor.setContextMenuAnchor(x, y);
8573 }
8574 return super.showContextMenu(x, y);
8575 }
8576
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008577 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08008578 * @return True iff this TextView contains a text that can be edited, or if this is
8579 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008580 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008581 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08008582 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07008583 }
8584
The Android Open Source Project4df24232009-03-05 14:34:35 -08008585 /**
8586 * Returns true, only while processing a touch gesture, if the initial
8587 * touch down event caused focus to move to the text view and as a result
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008588 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07008589 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08008590 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008591 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008592 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08008593 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008594
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008595 @Override
8596 public void cancelLongPress() {
8597 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008598 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008599 }
Gilles Debunne70a63122011-09-01 13:27:33 -07008600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008601 @Override
8602 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008603 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008604 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8605 return true;
8606 }
8607 }
8608
8609 return super.onTrackballEvent(event);
8610 }
8611
8612 public void setScroller(Scroller s) {
8613 mScroller = s;
8614 }
8615
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008616 @Override
8617 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008618 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8619 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008620 if (mMarquee != null && !mMarquee.isStopped()) {
8621 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07008622 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008623 final float scroll = marquee.getScroll();
8624 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07008625 } else {
8626 return 0.0f;
8627 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008628 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008629 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008630 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008631 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008632 case Gravity.LEFT:
8633 return 0.0f;
8634 case Gravity.RIGHT:
8635 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8636 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8637 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8638 case Gravity.CENTER_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008639 case Gravity.FILL_HORIZONTAL:
8640 final int textDirection = mLayout.getParagraphDirection(0);
8641 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8642 return 0.0f;
8643 } else {
8644 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8645 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8646 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8647 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008648 }
8649 }
8650 }
8651 return super.getLeftFadingEdgeStrength();
8652 }
8653
8654 @Override
8655 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07008656 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8657 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008658 if (mMarquee != null && !mMarquee.isStopped()) {
8659 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07008660 final float maxFadeScroll = marquee.getMaxFadeScroll();
8661 final float scroll = marquee.getScroll();
8662 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008663 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008664 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07008665 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07008666 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008667 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07008668 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8669 getCompoundPaddingRight();
8670 final float lineWidth = mLayout.getLineWidth(0);
8671 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008672 case Gravity.RIGHT:
8673 return 0.0f;
8674 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07008675 case Gravity.FILL_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07008676 final int textDirection = mLayout.getParagraphDirection(0);
8677 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8678 return 0.0f;
8679 } else {
8680 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008681 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8682 getHorizontalFadingEdgeLength();
Raph Levien8079ae12013-09-26 15:57:07 -07008683 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008684 }
8685 }
8686 }
8687 return super.getRightFadingEdgeStrength();
8688 }
8689
8690 @Override
8691 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07008692 if (mLayout != null) {
8693 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8694 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8695 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008696
8697 return super.computeHorizontalScrollRange();
8698 }
8699
8700 @Override
8701 protected int computeVerticalScrollRange() {
8702 if (mLayout != null)
8703 return mLayout.getHeight();
8704
8705 return super.computeVerticalScrollRange();
8706 }
8707
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07008708 @Override
8709 protected int computeVerticalScrollExtent() {
8710 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8711 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008712
8713 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07008714 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8715 super.findViewsWithText(outViews, searched, flags);
8716 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8717 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8718 String searchedLowerCase = searched.toString().toLowerCase();
8719 String textLowerCase = mText.toString().toLowerCase();
8720 if (textLowerCase.contains(searchedLowerCase)) {
8721 outViews.add(this);
8722 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008723 }
8724 }
8725
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008726 public enum BufferType {
8727 NORMAL, SPANNABLE, EDITABLE,
8728 }
8729
8730 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008731 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8732 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8733 * if TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008734 *
8735 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008736 */
8737 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
Alan Viverettefe167872014-09-16 15:41:27 -07008738 if (attrs == null) {
8739 // Preserve behavior prior to removal of this API.
8740 throw new NullPointerException();
8741 }
8742
Alan Viveretteabda5482014-06-27 16:10:54 -07008743 // It's not safe to use this method from apps. The parameter 'attrs'
8744 // must have been obtained using the TextView filter array which is not
8745 // available to the SDK. As such, we grab a default TypedArray with the
8746 // right filter instead here.
8747 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8748 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008749 if (colors == null) {
Alan Viveretteabda5482014-06-27 16:10:54 -07008750 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8751 if (ap != 0) {
8752 final TypedArray appearance = context.obtainStyledAttributes(
8753 ap, R.styleable.TextAppearance);
8754 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008755 appearance.recycle();
8756 }
8757 }
Alan Viveretteabda5482014-06-27 16:10:54 -07008758 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008759
8760 return colors;
8761 }
8762
8763 /**
Alan Viveretteabda5482014-06-27 16:10:54 -07008764 * Returns the default color from the TextView_textColor attribute from the
8765 * AttributeSet, if set, or the default color from the
8766 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8767 * TextView_textColor was not set directly.
Alan Viverette5171dee2014-09-11 16:33:01 -07008768 *
8769 * @removed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008770 */
Alan Viveretteabda5482014-06-27 16:10:54 -07008771 public static int getTextColor(Context context, TypedArray attrs, int def) {
8772 final ColorStateList colors = getTextColors(context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008773 if (colors == null) {
8774 return def;
8775 } else {
8776 return colors.getDefaultColor();
8777 }
8778 }
8779
8780 @Override
8781 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
James Cook9201e792015-02-11 10:46:44 -08008782 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8783 // Handle Ctrl-only shortcuts.
Jeff Brownc1df9072010-12-21 16:38:50 -08008784 switch (keyCode) {
8785 case KeyEvent.KEYCODE_A:
8786 if (canSelectText()) {
8787 return onTextContextMenuItem(ID_SELECT_ALL);
8788 }
8789 break;
James Cookf59152c2015-02-26 18:03:58 -08008790 case KeyEvent.KEYCODE_Z:
8791 if (canUndo()) {
8792 return onTextContextMenuItem(ID_UNDO);
8793 }
8794 break;
Jeff Brownc1df9072010-12-21 16:38:50 -08008795 case KeyEvent.KEYCODE_X:
8796 if (canCut()) {
8797 return onTextContextMenuItem(ID_CUT);
8798 }
8799 break;
8800 case KeyEvent.KEYCODE_C:
8801 if (canCopy()) {
8802 return onTextContextMenuItem(ID_COPY);
8803 }
8804 break;
8805 case KeyEvent.KEYCODE_V:
8806 if (canPaste()) {
8807 return onTextContextMenuItem(ID_PASTE);
8808 }
8809 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008810 }
James Cook9201e792015-02-11 10:46:44 -08008811 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8812 // Handle Ctrl-Shift shortcuts.
8813 switch (keyCode) {
James Cookf59152c2015-02-26 18:03:58 -08008814 case KeyEvent.KEYCODE_Z:
8815 if (canRedo()) {
8816 return onTextContextMenuItem(ID_REDO);
8817 }
8818 break;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09008819 case KeyEvent.KEYCODE_V:
8820 if (canPaste()) {
8821 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8822 }
James Cook9201e792015-02-11 10:46:44 -08008823 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008824 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008825 return super.onKeyShortcut(keyCode, event);
8826 }
8827
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008828 /**
8829 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8830 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07008831 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8832 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008833 */
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01008834 boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008835 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008836 }
8837
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008838 /**
8839 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8840 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008841 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008842 * See also {@link #canSelectText()}.
8843 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008844 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008845 // prepareCursorController() relies on this method.
8846 // If you change this condition, make sure prepareCursorController is called anywhere
8847 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008848 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008849 return isTextEditable() ||
8850 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008851 }
8852
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008853 private Locale getTextServicesLocale(boolean allowNullLocale) {
8854 // Start fetching the text services locale asynchronously.
8855 updateTextServicesLocaleAsync();
8856 // If !allowNullLocale and there is no cached text services locale, just return the default
8857 // locale.
8858 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8859 : mCurrentSpellCheckerLocaleCache;
8860 }
8861
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008862 /**
8863 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008864 * Caveat: This method may not return the latest text services locale, but this should be
8865 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008866 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008867 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008868 * in this TextView, based on the current spell checker settings,
8869 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008870 * Please note that a word iterator in this TextView is different from another word iterator
8871 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008872 * @hide
8873 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008874 // TODO: Support multi-locale
8875 // TODO: Update the text services locale immediately after the keyboard locale is switched
8876 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008877 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008878 return getTextServicesLocale(false /* allowNullLocale */);
8879 }
8880
8881 /**
Andrei Stingaceanub1891b32015-06-19 16:44:37 +01008882 * @return true if this TextView is specialized for showing and interacting with the extracted
8883 * text in a full-screen input method.
8884 * @hide
8885 */
8886 public boolean isInExtractedMode() {
8887 return false;
8888 }
8889
8890 /**
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008891 * This is a temporary method. Future versions may support multi-locale text.
8892 * Caveat: This method may not return the latest spell checker locale, but this should be
8893 * acceptable and it's more important to make this method asynchronous.
8894 *
8895 * @return The locale that should be used for a spell checker in this TextView,
8896 * based on the current spell checker settings, the current IME's locale, or the system default
8897 * locale.
8898 * @hide
8899 */
8900 public Locale getSpellCheckerLocale() {
8901 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008902 }
8903
8904 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008905 // AsyncTask.execute() uses a serial executor which means we don't have
8906 // to lock around updateTextServicesLocaleLocked() to prevent it from
8907 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008908 AsyncTask.execute(new Runnable() {
8909 @Override
8910 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008911 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008912 }
8913 });
8914 }
8915
8916 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008917 final TextServicesManager textServicesManager = (TextServicesManager)
8918 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8919 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008920 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008921 if (subtype != null) {
Yohei Yukawa658c29e2015-12-04 14:43:01 -08008922 locale = subtype.getLocaleObject();
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008923 } else {
8924 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008925 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008926 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008927 }
8928
8929 void onLocaleChanged() {
8930 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008931 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008932 }
8933
8934 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008935 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8936 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008937 * @hide
8938 */
8939 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008940 if (mEditor != null) {
8941 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008942 } else {
8943 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008944 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008945 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008946
Alan Viverettea54956a2015-01-07 16:05:02 -08008947 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008948 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08008949 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8950 super.onPopulateAccessibilityEventInternal(event);
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008951
Alan Viverette798253e2015-08-27 14:53:48 -04008952 final CharSequence text = getTextForAccessibility();
8953 if (!TextUtils.isEmpty(text)) {
8954 event.getText().add(text);
svetoslavganov75986cf2009-05-14 22:28:01 -07008955 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008956 }
8957
alanv7d624192012-05-21 14:23:17 -07008958 /**
8959 * @return true if the user has explicitly allowed accessibility services
8960 * to speak passwords.
8961 */
8962 private boolean shouldSpeakPasswordsForAccessibility() {
Alan Viveretteb6e0cb92014-11-24 15:13:43 -08008963 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
Alan Viverette97524282014-12-02 16:24:24 -08008964 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8965 UserHandle.USER_CURRENT_OR_SELF) == 1);
alanv7d624192012-05-21 14:23:17 -07008966 }
8967
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008968 @Override
8969 public CharSequence getAccessibilityClassName() {
8970 return TextView.class.getName();
8971 }
8972
8973 @Override
Dianne Hackborn49b043f2015-05-07 14:21:38 -07008974 public void onProvideStructure(ViewStructure structure) {
8975 super.onProvideStructure(structure);
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07008976 final boolean isPassword = hasPasswordTransformationMethod()
8977 || isPasswordInputType(getInputType());
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08008978 if (!isPassword) {
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07008979 if (mLayout == null) {
8980 assumeLayout();
8981 }
8982 Layout layout = mLayout;
8983 final int lineCount = layout.getLineCount();
8984 if (lineCount <= 1) {
8985 // Simple case: this is a single line.
8986 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8987 } else {
8988 // Complex case: multi-line, could be scrolled or within a scroll container
8989 // so some lines are not visible.
8990 final int[] tmpCords = new int[2];
8991 getLocationInWindow(tmpCords);
8992 final int topWindowLocation = tmpCords[1];
8993 View root = this;
8994 ViewParent viewParent = getParent();
8995 while (viewParent instanceof View) {
8996 root = (View) viewParent;
8997 viewParent = root.getParent();
8998 }
8999 final int windowHeight = root.getHeight();
9000 final int topLine;
9001 final int bottomLine;
9002 if (topWindowLocation >= 0) {
9003 // The top of the view is fully within its window; start text at line 0.
9004 topLine = getLineAtCoordinateUnclamped(0);
9005 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
9006 } else {
9007 // The top of hte window has scrolled off the top of the window; figure out
9008 // the starting line for this.
9009 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
9010 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
9011 }
9012 // We want to return some contextual lines above/below the lines that are
9013 // actually visible.
9014 int expandedTopLine = topLine - (bottomLine-topLine)/2;
9015 if (expandedTopLine < 0) {
9016 expandedTopLine = 0;
9017 }
9018 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
9019 if (expandedBottomLine >= lineCount) {
9020 expandedBottomLine = lineCount-1;
9021 }
9022 // Convert lines into character offsets.
9023 int expandedTopChar = layout.getLineStart(expandedTopLine);
9024 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
9025 // Take into account selection -- if there is a selection, we need to expand
9026 // the text we are returning to include that selection.
9027 final int selStart = getSelectionStart();
9028 final int selEnd = getSelectionEnd();
9029 if (selStart < selEnd) {
9030 if (selStart < expandedTopChar) {
9031 expandedTopChar = selStart;
9032 }
9033 if (selEnd > expandedBottomChar) {
9034 expandedBottomChar = selEnd;
9035 }
9036 }
9037 // Get the text and trim it to the range we are reporting.
9038 CharSequence text = getText();
9039 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
9040 text = text.subSequence(expandedTopChar, expandedBottomChar);
9041 }
9042 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
9043 final int[] lineOffsets = new int[bottomLine-topLine+1];
9044 final int[] lineBaselines = new int[bottomLine-topLine+1];
9045 final int baselineOffset = getBaselineOffset();
9046 for (int i=topLine; i<=bottomLine; i++) {
9047 lineOffsets[i-topLine] = layout.getLineStart(i);
9048 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
9049 }
9050 structure.setTextLines(lineOffsets, lineBaselines);
9051 }
James Cook5cfaae42015-05-28 15:52:44 -07009052
9053 // Extract style information that applies to the TextView as a whole.
9054 int style = 0;
9055 int typefaceStyle = getTypefaceStyle();
9056 if ((typefaceStyle & Typeface.BOLD) != 0) {
9057 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9058 }
9059 if ((typefaceStyle & Typeface.ITALIC) != 0) {
9060 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
9061 }
9062
9063 // Global styles can also be set via TextView.setPaintFlags().
9064 int paintFlags = mTextPaint.getFlags();
9065 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
9066 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
9067 }
9068 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
9069 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
9070 }
9071 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
9072 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
9073 }
9074
9075 // TextView does not have its own text background color. A background is either part
9076 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
9077 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
9078 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08009079 }
Dianne Hackborna83ce1d2015-03-11 15:16:13 -07009080 structure.setHint(getHint());
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -08009081 }
9082
Alan Viverettea54956a2015-01-07 16:05:02 -08009083 /** @hide */
Svetoslav Ganov30401322011-05-12 18:53:45 -07009084 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009085 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
9086 super.onInitializeAccessibilityEventInternal(event);
Svetoslav Ganov30401322011-05-12 18:53:45 -07009087
9088 final boolean isPassword = hasPasswordTransformationMethod();
9089 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07009090
9091 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
9092 event.setFromIndex(Selection.getSelectionStart(mText));
9093 event.setToIndex(Selection.getSelectionEnd(mText));
9094 event.setItemCount(mText.length());
9095 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07009096 }
9097
Alan Viverettea54956a2015-01-07 16:05:02 -08009098 /** @hide */
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009099 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009100 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
9101 super.onInitializeAccessibilityNodeInfoInternal(info);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009102
9103 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08009104 info.setPassword(isPassword);
Alan Viverette798253e2015-08-27 14:53:48 -04009105 info.setText(getTextForAccessibility());
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009106
Svetoslavbcc46a02013-02-06 11:56:00 -08009107 if (mBufferType == BufferType.EDITABLE) {
9108 info.setEditable(true);
9109 }
9110
Svetoslav6254f482013-06-04 17:22:14 -07009111 if (mEditor != null) {
9112 info.setInputType(mEditor.mInputType);
Alan Viverette5b2081d2013-08-28 10:43:07 -07009113
9114 if (mEditor.mError != null) {
9115 info.setContentInvalid(true);
Alan Viverettefccbff52014-07-07 15:06:14 -07009116 info.setError(mEditor.mError);
Alan Viverette5b2081d2013-08-28 10:43:07 -07009117 }
Svetoslav6254f482013-06-04 17:22:14 -07009118 }
9119
Svetoslavdb7da0e2013-04-22 18:34:02 -07009120 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009121 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
9122 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
9123 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
9124 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
9125 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
9126 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
9127 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009128 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07009129 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07009130
Svetoslav7c512842013-01-30 23:02:08 -08009131 if (isFocused()) {
Svetoslav7c512842013-01-30 23:02:08 -08009132 if (canCopy()) {
9133 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
9134 }
9135 if (canPaste()) {
9136 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
9137 }
9138 if (canCut()) {
9139 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
9140 }
Abodunrinwa Toki0e5c6032015-06-26 14:11:21 -07009141 if (canShare()) {
9142 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
9143 ACCESSIBILITY_ACTION_SHARE,
9144 getResources().getString(com.android.internal.R.string.share)));
9145 }
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009146 if (canProcessText()) { // also implies mEditor is not null.
9147 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
9148 }
Svetoslav7c512842013-01-30 23:02:08 -08009149 }
Alan Viverette058ac7c2013-08-19 16:44:30 -07009150
Alan Viverette029942f2014-08-12 14:55:56 -07009151 // Check for known input filter types.
9152 final int numFilters = mFilters.length;
9153 for (int i = 0; i < numFilters; i++) {
9154 final InputFilter filter = mFilters[i];
9155 if (filter instanceof InputFilter.LengthFilter) {
9156 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
9157 }
9158 }
9159
Alan Viverette058ac7c2013-08-19 16:44:30 -07009160 if (!isSingleLine()) {
9161 info.setMultiLine(true);
9162 }
Svetoslav7c512842013-01-30 23:02:08 -08009163 }
9164
Alan Viverettecd305ae2014-12-12 14:13:24 -08009165 /**
9166 * Performs an accessibility action after it has been offered to the
9167 * delegate.
9168 *
9169 * @hide
9170 */
Svetoslav7c512842013-01-30 23:02:08 -08009171 @Override
Alan Viverettecd305ae2014-12-12 14:13:24 -08009172 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009173 if (mEditor != null
9174 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
9175 return true;
9176 }
Svetoslav7c512842013-01-30 23:02:08 -08009177 switch (action) {
Alan Viveretteb6877882014-09-07 15:49:49 -07009178 case AccessibilityNodeInfo.ACTION_CLICK: {
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009179 return performAccessibilityActionClick(arguments);
Alan Viveretteb6877882014-09-07 15:49:49 -07009180 }
Svetoslav7c512842013-01-30 23:02:08 -08009181 case AccessibilityNodeInfo.ACTION_COPY: {
9182 if (isFocused() && canCopy()) {
9183 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08009184 return true;
9185 }
9186 }
9187 } return false;
9188 case AccessibilityNodeInfo.ACTION_PASTE: {
9189 if (isFocused() && canPaste()) {
9190 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08009191 return true;
9192 }
9193 }
9194 } return false;
9195 case AccessibilityNodeInfo.ACTION_CUT: {
9196 if (isFocused() && canCut()) {
9197 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08009198 return true;
9199 }
9200 }
9201 } return false;
9202 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009203 ensureIterableTextForAccessibilitySelectable();
9204 CharSequence text = getIterableTextForAccessibility();
9205 if (text == null) {
9206 return false;
9207 }
9208 final int start = (arguments != null) ? arguments.getInt(
9209 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9210 final int end = (arguments != null) ? arguments.getInt(
9211 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9212 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9213 // No arguments clears the selection.
9214 if (start == end && end == -1) {
9215 Selection.removeSelection((Spannable) text);
9216 return true;
Svetoslav7c512842013-01-30 23:02:08 -08009217 }
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009218 if (start >= 0 && start <= end && end <= text.length()) {
9219 Selection.setSelection((Spannable) text, start, end);
9220 // Make sure selection mode is engaged.
9221 if (mEditor != null) {
9222 mEditor.startSelectionActionMode();
Svetoslav7c512842013-01-30 23:02:08 -08009223 }
Maxim Bogatov1fa819b2015-06-25 18:05:34 -07009224 return true;
Svetoslav7c512842013-01-30 23:02:08 -08009225 }
9226 }
9227 } return false;
Svet Ganov9e1c67e2014-10-14 08:53:33 -07009228 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9229 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9230 ensureIterableTextForAccessibilitySelectable();
Alan Viverettecd305ae2014-12-12 14:13:24 -08009231 return super.performAccessibilityActionInternal(action, arguments);
Svet Ganov9e1c67e2014-10-14 08:53:33 -07009232 }
Abodunrinwa Toki0e5c6032015-06-26 14:11:21 -07009233 case ACCESSIBILITY_ACTION_SHARE: {
9234 if (isFocused() && canShare()) {
9235 if (onTextContextMenuItem(ID_SHARE)) {
9236 return true;
9237 }
9238 }
9239 } return false;
Svetoslav7c512842013-01-30 23:02:08 -08009240 default: {
Alan Viverettecd305ae2014-12-12 14:13:24 -08009241 return super.performAccessibilityActionInternal(action, arguments);
Svetoslav7c512842013-01-30 23:02:08 -08009242 }
9243 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07009244 }
9245
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009246 private boolean performAccessibilityActionClick(Bundle arguments) {
9247 boolean handled = false;
9248 boolean processed = false;
9249
9250 if (!isEnabled()) {
9251 return false;
9252 }
9253
9254 if (arguments != null && arguments.containsKey(
9255 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT)) {
9256 int spanIndex = arguments.getInt(
9257 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_SPAN_INDEX_INT, -1);
9258 if (spanIndex >= 0 && hasSpannableText()) {
9259 ClickableSpan[] spans = ((Spannable) mText).getSpans(0,
9260 mText.length(), ClickableSpan.class);
9261 if (spans != null && spans.length > spanIndex && spans[spanIndex] != null) {
9262 // Simulate View.onTouchEvent for an ACTION_UP event
9263 if (isFocusable() && !isFocused()) {
9264 requestFocus();
9265 }
9266 spans[spanIndex].onClick(this);
9267 handled = true;
9268 }
9269 }
9270 processed = true;
9271 }
9272
9273 if (!processed && arguments != null && arguments.containsKey(
9274 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT)) {
9275 int characterIndex = arguments.getInt(
9276 AccessibilityNodeInfo.ACTION_ARGUMENT_CLICK_CHARACTER_INDEX_INT, -1);
9277 if (characterIndex >= 0 && hasSpannableText()) {
9278 ClickableSpan[] spans = ((Spannable) mText).getSpans(characterIndex,
9279 characterIndex, ClickableSpan.class);
9280 // click only on the first span to keep parity with onTouch() implementation
9281 if (spans != null && spans.length > 0 && spans[0] != null) {
9282 // Simulate View.onTouchEvent for an ACTION_UP event
9283 if (isFocusable() && !isFocused()) {
9284 requestFocus();
9285 }
9286 spans[0].onClick(this);
9287 handled = true;
9288 }
9289 }
9290 processed = true;
9291 }
9292
9293 if (!processed && (isClickable() || isLongClickable())) {
9294 // Simulate View.onTouchEvent for an ACTION_UP event
9295 if (isFocusable() && !isFocused()) {
9296 requestFocus();
9297 }
9298
9299 performClick();
9300 handled = true;
9301 }
9302
9303 // Show the IME, except when selecting in read-only text.
9304 if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null
9305 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9306 final InputMethodManager imm = InputMethodManager.peekInstance();
9307 viewClicked(imm);
9308 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9309 handled |= imm.showSoftInput(this, 0);
9310 }
9311 }
9312
9313 return handled;
9314 }
9315
9316 private boolean hasSpannableText() {
9317 return mText != null && mText instanceof Spannable;
9318 }
9319
Alan Viverettea54956a2015-01-07 16:05:02 -08009320 /** @hide */
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009321 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -08009322 public void sendAccessibilityEventInternal(int eventType) {
Abodunrinwa Tokideaf0db2015-06-26 18:21:30 -07009323 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9324 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9325 }
9326
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009327 // Do not send scroll events since first they are not interesting for
9328 // accessibility and second such events a generated too frequently.
9329 // For details see the implementation of bringTextIntoView().
9330 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9331 return;
9332 }
Alan Viverettea54956a2015-01-07 16:05:02 -08009333 super.sendAccessibilityEventInternal(eventType);
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07009334 }
9335
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009336 /**
Alan Viverette798253e2015-08-27 14:53:48 -04009337 * Returns the text that should be exposed to accessibility services.
9338 * <p>
9339 * This approximates what is displayed visually. If the user has specified
9340 * that accessibility services should speak passwords, this method will
9341 * bypass any password transformation method and return unobscured text.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009342 *
Alan Viverette798253e2015-08-27 14:53:48 -04009343 * @return the text that should be exposed to accessibility services, may
9344 * be {@code null} if no text is set
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009345 */
Alan Viverette798253e2015-08-27 14:53:48 -04009346 @Nullable
9347 private CharSequence getTextForAccessibility() {
9348 // If the text is empty, we must be showing the hint text.
9349 if (TextUtils.isEmpty(mText)) {
9350 return mHint;
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009351 }
Alan Viverette798253e2015-08-27 14:53:48 -04009352
9353 // Check whether we need to bypass the transformation
9354 // method and expose unobscured text.
9355 if (hasPasswordTransformationMethod() && shouldSpeakPasswordsForAccessibility()) {
9356 return mText;
9357 }
9358
9359 // Otherwise, speak whatever text is being displayed.
9360 return mTransformed;
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07009361 }
9362
svetoslavganov75986cf2009-05-14 22:28:01 -07009363 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9364 int fromIndex, int removedCount, int addedCount) {
9365 AccessibilityEvent event =
Maxim Bogatov219b41d2015-05-01 14:00:24 -07009366 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
svetoslavganov75986cf2009-05-14 22:28:01 -07009367 event.setFromIndex(fromIndex);
9368 event.setRemovedCount(removedCount);
9369 event.setAddedCount(addedCount);
9370 event.setBeforeText(beforeText);
9371 sendAccessibilityEventUnchecked(event);
9372 }
9373
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009374 /**
9375 * Returns whether this text view is a current input method target. The
9376 * default implementation just checks with {@link InputMethodManager}.
9377 */
9378 public boolean isInputMethodTarget() {
9379 InputMethodManager imm = InputMethodManager.peekInstance();
9380 return imm != null && imm.isActive(this);
9381 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07009382
Gilles Debunned88876a2012-03-16 17:34:04 -07009383 static final int ID_SELECT_ALL = android.R.id.selectAll;
James Cookf59152c2015-02-26 18:03:58 -08009384 static final int ID_UNDO = android.R.id.undo;
9385 static final int ID_REDO = android.R.id.redo;
Gilles Debunned88876a2012-03-16 17:34:04 -07009386 static final int ID_CUT = android.R.id.cut;
9387 static final int ID_COPY = android.R.id.copy;
9388 static final int ID_PASTE = android.R.id.paste;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009389 static final int ID_SHARE = android.R.id.shareText;
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009390 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
Clara Bayarrif84a9722015-03-02 16:09:09 +00009391 static final int ID_REPLACE = android.R.id.replaceText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009392
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009393 /**
9394 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07009395 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009396 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07009397 *
9398 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009399 */
9400 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009401 int min = 0;
9402 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07009403
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009404 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07009405 final int selStart = getSelectionStart();
9406 final int selEnd = getSelectionEnd();
9407
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07009408 min = Math.max(0, Math.min(selStart, selEnd));
9409 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009410 }
9411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009412 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08009413 case ID_SELECT_ALL:
Clara Bayarri01243ac2015-06-03 00:46:29 +01009414 // This starts an action mode if triggered from another action mode. Text is
9415 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9416 // true even if text is empty.
9417 boolean shouldRestartActionMode =
9418 mEditor != null && mEditor.mTextActionMode != null;
9419 stopTextActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07009420 selectAllText();
Clara Bayarri01243ac2015-06-03 00:46:29 +01009421 if (shouldRestartActionMode) {
9422 mEditor.startSelectionActionMode();
9423 }
Jeff Brownc1df9072010-12-21 16:38:50 -08009424 return true;
9425
James Cookf59152c2015-02-26 18:03:58 -08009426 case ID_UNDO:
9427 if (mEditor != null) {
9428 mEditor.undo();
9429 }
9430 return true; // Returns true even if nothing was undone.
9431
9432 case ID_REDO:
9433 if (mEditor != null) {
9434 mEditor.redo();
9435 }
9436 return true; // Returns true even if nothing was undone.
9437
Jeff Brownc1df9072010-12-21 16:38:50 -08009438 case ID_PASTE:
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009439 paste(min, max, true /* withFormatting */);
9440 return true;
9441
9442 case ID_PASTE_AS_PLAIN_TEXT:
9443 paste(min, max, false /* withFormatting */);
Jeff Brownc1df9072010-12-21 16:38:50 -08009444 return true;
9445
9446 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009447 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01009448 deleteText_internal(min, max);
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009449 stopTextActionMode();
Jeff Brownc1df9072010-12-21 16:38:50 -08009450 return true;
9451
9452 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009453 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009454 stopTextActionMode();
Jeff Brownc1df9072010-12-21 16:38:50 -08009455 return true;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009456
Andrei Stingaceanueeb9afc2015-05-12 12:39:07 +01009457 case ID_REPLACE:
9458 if (mEditor != null) {
9459 mEditor.replace();
9460 }
9461 return true;
9462
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009463 case ID_SHARE:
9464 shareSelectedText();
9465 return true;
Jeff Brownc1df9072010-12-21 16:38:50 -08009466 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009467 return false;
9468 }
9469
Gilles Debunned88876a2012-03-16 17:34:04 -07009470 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07009471 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9472 }
9473
Gilles Debunnee15b3582010-06-16 15:17:21 -07009474 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009475 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009476 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07009477
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09009478 if (mEditor != null) {
9479 mEditor.mIsBeingLongClicked = true;
9480 }
9481
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009482 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009483 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009484 }
Gilles Debunnef170a342010-11-11 11:08:59 -08009485
Gilles Debunned88876a2012-03-16 17:34:04 -07009486 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009487 handled |= mEditor.performLongClick(handled);
Keisuke Kuroyanagid0560812015-12-17 17:50:42 +09009488 mEditor.mIsBeingLongClicked = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07009489 }
9490
Gilles Debunne9f102ca2012-02-28 11:15:54 -08009491 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07009492 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07009493 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07009494 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009495
Gilles Debunne299733e2011-02-07 17:11:41 -08009496 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009497 }
9498
Gilles Debunne60e21862012-01-30 15:04:14 -08009499 @Override
9500 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9501 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08009502 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009503 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08009504 }
9505 }
9506
9507 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009508 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9509 * by the IME or by the spell checker as the user types. This is done by adding
9510 * {@link SuggestionSpan}s to the text.
9511 *
9512 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9513 * user asks for them on these parts of the text. This value depends on the inputType of this
9514 * TextView.
9515 *
9516 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9517 *
9518 * In addition, the type variation must be one of
9519 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9520 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9521 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9522 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9523 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9524 *
9525 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9526 *
9527 * @return true if the suggestions popup window is enabled, based on the inputType.
9528 */
9529 public boolean isSuggestionsEnabled() {
9530 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009531 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9532 return false;
9533 }
9534 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009535
Gilles Debunne2d373a12012-04-20 15:32:19 -07009536 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08009537 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9538 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9539 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9540 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9541 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9542 }
9543
9544 /**
9545 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9546 * selection is initiated in this View.
9547 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009548 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009549 * Paste, Replace and Share actions, depending on what this View supports.
Gilles Debunne60e21862012-01-30 15:04:14 -08009550 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009551 * <p>A custom implementation can add new entries in the default menu in its
9552 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9553 * method. The default actions can also be removed from the menu using
James Cookd2026682015-03-03 14:40:14 -08009554 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009555 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9556 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
Gilles Debunne60e21862012-01-30 15:04:14 -08009557 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009558 * <p>Returning false from
9559 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9560 * will prevent the action mode from being started.
Gilles Debunne60e21862012-01-30 15:04:14 -08009561 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009562 * <p>Action click events should be handled by the custom implementation of
9563 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9564 * android.view.MenuItem)}.
Gilles Debunne60e21862012-01-30 15:04:14 -08009565 *
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009566 * <p>Note that text selection mode is not started when a TextView receives focus and the
Gilles Debunne60e21862012-01-30 15:04:14 -08009567 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9568 * that case, to allow for quick replacement.
9569 */
9570 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07009571 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07009572 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009573 }
9574
9575 /**
9576 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9577 *
9578 * @return The current custom selection callback.
9579 */
9580 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07009581 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08009582 }
9583
9584 /**
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009585 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9586 * insertion is initiated in this View.
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009587 * The standard implementation populates the menu with a subset of Select All,
9588 * Paste and Replace actions, depending on what this View supports.
9589 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009590 * <p>A custom implementation can add new entries in the default menu in its
9591 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9592 * android.view.Menu)} method. The default actions can also be removed from the menu using
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009593 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009594 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009595 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009596 * <p>Returning false from
9597 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9598 * android.view.Menu)} will prevent the action mode from being started.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009599 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009600 * <p>Action click events should be handled by the custom implementation of
9601 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9602 * android.view.MenuItem)}.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009603 *
Dianne Hackbornece0f4f2015-06-11 13:29:01 -07009604 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9605 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009606 */
9607 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9608 createEditorIfNeeded();
9609 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9610 }
9611
9612 /**
9613 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9614 *
9615 * @return The current custom insertion callback.
9616 */
9617 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9618 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9619 }
9620
9621 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009622 * @hide
9623 */
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009624 protected void stopTextActionMode() {
James Cookf59152c2015-02-26 18:03:58 -08009625 if (mEditor != null) {
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009626 mEditor.stopTextActionMode();
James Cookf59152c2015-02-26 18:03:58 -08009627 }
9628 }
9629
9630 boolean canUndo() {
9631 return mEditor != null && mEditor.canUndo();
9632 }
9633
9634 boolean canRedo() {
9635 return mEditor != null && mEditor.canRedo();
Gilles Debunned88876a2012-03-16 17:34:04 -07009636 }
9637
9638 boolean canCut() {
9639 if (hasPasswordTransformationMethod()) {
9640 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08009641 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009642
Gilles Debunne2d373a12012-04-20 15:32:19 -07009643 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9644 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009645 return true;
9646 }
9647
9648 return false;
9649 }
9650
9651 boolean canCopy() {
9652 if (hasPasswordTransformationMethod()) {
9653 return false;
9654 }
9655
Victoria Leased849f542014-01-15 15:15:03 -08009656 if (mText.length() > 0 && hasSelection() && mEditor != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07009657 return true;
9658 }
9659
9660 return false;
9661 }
9662
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009663 boolean canShare() {
Adam Powellbd918aad2016-01-21 10:49:39 -08009664 return canCopy() && isDeviceProvisioned();
9665 }
9666
9667 boolean isDeviceProvisioned() {
9668 if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) {
9669 mDeviceProvisionedState = Settings.Global.getInt(
9670 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0
9671 ? DEVICE_PROVISIONED_YES
9672 : DEVICE_PROVISIONED_NO;
9673 }
9674 return mDeviceProvisionedState == DEVICE_PROVISIONED_YES;
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009675 }
9676
Gilles Debunned88876a2012-03-16 17:34:04 -07009677 boolean canPaste() {
9678 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07009679 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07009680 getSelectionStart() >= 0 &&
9681 getSelectionEnd() >= 0 &&
9682 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9683 hasPrimaryClip());
9684 }
9685
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009686 boolean canProcessText() {
9687 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9688 || hasPasswordTransformationMethod()) {
9689 return false;
9690 }
9691
9692 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9693 return true;
9694 }
9695
9696 return false;
9697 }
9698
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01009699 boolean canSelectAllText() {
Clara Bayarri3b69fd82015-06-03 21:52:02 +01009700 return canSelectText() && !hasPasswordTransformationMethod()
9701 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
Andrei Stingaceanu47f82ae2015-04-28 17:43:54 +01009702 }
9703
Gilles Debunned88876a2012-03-16 17:34:04 -07009704 boolean selectAllText() {
Seigo Nonakabb6a62c2015-03-31 21:59:30 +09009705 // Need to hide insert point cursor controller before settings selection, otherwise insert
9706 // point cursor controller obtains cursor update event and update cursor with cancelling
9707 // selection.
9708 if (mEditor != null) {
9709 mEditor.hideInsertionPointCursorController();
9710 }
Gilles Debunned88876a2012-03-16 17:34:04 -07009711 final int length = mText.length();
9712 Selection.setSelection((Spannable) mText, 0, length);
9713 return length > 0;
9714 }
9715
Clara Bayarrid5bf3ed2015-03-27 17:32:45 +00009716 void replaceSelectionWithText(CharSequence text) {
9717 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9718 }
9719
Gilles Debunned88876a2012-03-16 17:34:04 -07009720 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08009721 * Paste clipboard content between min and max positions.
9722 */
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009723 private void paste(int min, int max, boolean withFormatting) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009724 ClipboardManager clipboard =
9725 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9726 ClipData clip = clipboard.getPrimaryClip();
9727 if (clip != null) {
9728 boolean didFirst = false;
9729 for (int i=0; i<clip.getItemCount(); i++) {
Keisuke Kuroyanagib103709d2015-02-24 12:51:46 +09009730 final CharSequence paste;
9731 if (withFormatting) {
9732 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9733 } else {
9734 // Get an item as text and remove all spans by toString().
9735 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9736 paste = (text instanceof Spanned) ? text.toString() : text;
9737 }
Gilles Debunne60e21862012-01-30 15:04:14 -08009738 if (paste != null) {
9739 if (!didFirst) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009740 Selection.setSelection((Spannable) mText, max);
9741 ((Editable) mText).replace(min, max, paste);
9742 didFirst = true;
9743 } else {
9744 ((Editable) mText).insert(getSelectionEnd(), "\n");
9745 ((Editable) mText).insert(getSelectionEnd(), paste);
9746 }
9747 }
9748 }
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009749 stopTextActionMode();
Andrei Stingaceanu77b9c382015-05-06 13:25:19 +01009750 sLastCutCopyOrTextChangedTime = 0;
Gilles Debunne60e21862012-01-30 15:04:14 -08009751 }
9752 }
9753
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009754 private void shareSelectedText() {
9755 String selectedText = getSelectedText();
9756 if (selectedText != null && !selectedText.isEmpty()) {
9757 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9758 sharingIntent.setType("text/plain");
9759 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9760 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9761 getContext().startActivity(Intent.createChooser(sharingIntent, null));
Clara Bayarri7938cdb2015-06-02 20:03:45 +01009762 stopTextActionMode();
Andrei Stingaceanu7f0c5bd2015-04-14 17:12:08 +01009763 }
9764 }
9765
Gilles Debunne60e21862012-01-30 15:04:14 -08009766 private void setPrimaryClip(ClipData clip) {
9767 ClipboardManager clipboard = (ClipboardManager) getContext().
9768 getSystemService(Context.CLIPBOARD_SERVICE);
9769 clipboard.setPrimaryClip(clip);
Andrei Stingaceanu77b9c382015-05-06 13:25:19 +01009770 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
Gilles Debunne60e21862012-01-30 15:04:14 -08009771 }
9772
Gilles Debunne60e21862012-01-30 15:04:14 -08009773 /**
9774 * Get the character offset closest to the specified absolute position. A typical use case is to
9775 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9776 *
9777 * @param x The horizontal absolute position of a point on screen
9778 * @param y The vertical absolute position of a point on screen
9779 * @return the character offset for the character whose position is closest to the specified
9780 * position. Returns -1 if there is no layout.
9781 */
9782 public int getOffsetForPosition(float x, float y) {
9783 if (getLayout() == null) return -1;
9784 final int line = getLineAtCoordinate(y);
9785 final int offset = getOffsetAtCoordinate(line, x);
9786 return offset;
9787 }
9788
Gilles Debunned88876a2012-03-16 17:34:04 -07009789 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009790 x -= getTotalPaddingLeft();
9791 // Clamp the position to inside of the view.
9792 x = Math.max(0.0f, x);
9793 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9794 x += getScrollX();
9795 return x;
9796 }
9797
Gilles Debunned88876a2012-03-16 17:34:04 -07009798 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009799 y -= getTotalPaddingTop();
9800 // Clamp the position to inside of the view.
9801 y = Math.max(0.0f, y);
9802 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9803 y += getScrollY();
9804 return getLayout().getLineForVertical((int) y);
9805 }
9806
Dianne Hackborn6f0fdc42015-07-07 14:29:36 -07009807 int getLineAtCoordinateUnclamped(float y) {
9808 y -= getTotalPaddingTop();
9809 y += getScrollY();
9810 return getLayout().getLineForVertical((int) y);
9811 }
9812
Mady Mellor2ff2cd82015-03-02 10:37:01 -08009813 int getOffsetAtCoordinate(int line, float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009814 x = convertToLocalHorizontalCoordinate(x);
9815 return getLayout().getOffsetForHorizontal(line, x);
9816 }
9817
Gilles Debunne60e21862012-01-30 15:04:14 -08009818 @Override
9819 public boolean onDragEvent(DragEvent event) {
9820 switch (event.getAction()) {
9821 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009822 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08009823
9824 case DragEvent.ACTION_DRAG_ENTERED:
9825 TextView.this.requestFocus();
9826 return true;
9827
9828 case DragEvent.ACTION_DRAG_LOCATION:
9829 final int offset = getOffsetForPosition(event.getX(), event.getY());
9830 Selection.setSelection((Spannable)mText, offset);
9831 return true;
9832
9833 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07009834 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08009835 return true;
9836
9837 case DragEvent.ACTION_DRAG_ENDED:
9838 case DragEvent.ACTION_DRAG_EXITED:
9839 default:
9840 return true;
9841 }
9842 }
9843
Gilles Debunne60e21862012-01-30 15:04:14 -08009844 boolean isInBatchEditMode() {
9845 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07009846 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08009847 if (ims != null) {
9848 return ims.mBatchEditNesting > 0;
9849 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07009850 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08009851 }
9852
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009853 @Override
9854 public void onRtlPropertiesChanged(int layoutDirection) {
9855 super.onRtlPropertiesChanged(layoutDirection);
9856
Keisuke Kuroyanagi17b0bda2015-06-19 15:24:07 +09009857 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9858 if (mTextDir != newTextDir) {
9859 mTextDir = newTextDir;
9860 if (mLayout != null) {
9861 checkForRelayout();
9862 }
Fabrice Di Meglio22228fe2014-01-06 16:30:43 -08009863 }
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07009864 }
9865
Selim Cinek4fb12d32015-11-19 18:10:48 -08009866 /**
9867 * @hide
9868 */
9869 protected TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08009870 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08009871 // passwords fields should be LTR
9872 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009873 }
9874
9875 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07009876 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08009877
9878 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07009879 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009880 default:
9881 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009882 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08009883 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08009884 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009885 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009886 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009887 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08009888 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009889 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009890 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009891 return TextDirectionHeuristics.LOCALE;
Roozbeh Pournaderb51222a2015-04-13 14:33:40 -07009892 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9893 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9894 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9895 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08009896 }
9897 }
9898
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07009899 /**
9900 * @hide
9901 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009902 @Override
9903 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009904 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009905 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009906 return;
9907 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009908 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08009909
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009910 // Resolve drawables
9911 if (mDrawables != null) {
Alan Viverette189d4f52015-12-16 16:06:23 -05009912 if (mDrawables.resolveWithLayoutDirection(layoutDirection)) {
9913 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]);
9914 prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]);
9915 applyCompoundDrawableTint();
9916 }
9917 }
9918 }
9919
9920 /**
9921 * Prepares a drawable for display by propagating layout direction and
9922 * drawable state.
9923 *
9924 * @param dr the drawable to prepare
9925 */
9926 private void prepareDrawableForDisplay(@Nullable Drawable dr) {
9927 if (dr == null) {
9928 return;
9929 }
9930
9931 dr.setLayoutDirection(getLayoutDirection());
9932
9933 if (dr.isStateful()) {
9934 dr.setState(getDrawableState());
9935 dr.jumpToCurrentState();
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07009936 }
9937 }
9938
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07009939 /**
9940 * @hide
9941 */
Gilles Debunne60e21862012-01-30 15:04:14 -08009942 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08009943 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07009944 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08009945 }
9946
9947 /**
9948 * @hide
9949 */
9950 protected void viewClicked(InputMethodManager imm) {
9951 if (imm != null) {
9952 imm.viewClicked(this);
9953 }
9954 }
9955
9956 /**
9957 * Deletes the range of text [start, end[.
9958 * @hide
9959 */
9960 protected void deleteText_internal(int start, int end) {
9961 ((Editable) mText).delete(start, end);
9962 }
9963
9964 /**
9965 * Replaces the range of text [start, end[ by replacement text
9966 * @hide
9967 */
9968 protected void replaceText_internal(int start, int end, CharSequence text) {
9969 ((Editable) mText).replace(start, end, text);
9970 }
9971
9972 /**
9973 * Sets a span on the specified range of text
9974 * @hide
9975 */
9976 protected void setSpan_internal(Object span, int start, int end, int flags) {
9977 ((Editable) mText).setSpan(span, start, end, flags);
9978 }
9979
9980 /**
9981 * Moves the cursor to the specified offset position in text
9982 * @hide
9983 */
9984 protected void setCursorPosition_internal(int start, int end) {
9985 Selection.setSelection(((Editable) mText), start, end);
9986 }
9987
9988 /**
9989 * An Editor should be created as soon as any of the editable-specific fields (grouped
9990 * inside the Editor object) is assigned to a non-default value.
9991 * This method will create the Editor if needed.
9992 *
9993 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9994 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9995 * Editor for backward compatibility, as soon as one of these fields is assigned.
9996 *
9997 * Also note that for performance reasons, the mEditor is created when needed, but not
9998 * reset when no more edit-specific fields are needed.
9999 */
Gilles Debunne5fae9962012-05-08 14:53:20 -070010000 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -080010001 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -070010002 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -080010003 }
10004 }
10005
Gilles Debunne60e21862012-01-30 15:04:14 -080010006 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010007 * @hide
10008 */
10009 @Override
10010 public CharSequence getIterableTextForAccessibility() {
Svet Ganov9e1c67e2014-10-14 08:53:33 -070010011 return mText;
10012 }
10013
10014 private void ensureIterableTextForAccessibilitySelectable() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010015 if (!(mText instanceof Spannable)) {
10016 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010017 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010018 }
10019
10020 /**
10021 * @hide
10022 */
10023 @Override
10024 public TextSegmentIterator getIteratorForGranularity(int granularity) {
10025 switch (granularity) {
10026 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
10027 Spannable text = (Spannable) getIterableTextForAccessibility();
10028 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10029 AccessibilityIterators.LineTextSegmentIterator iterator =
10030 AccessibilityIterators.LineTextSegmentIterator.getInstance();
10031 iterator.initialize(text, getLayout());
10032 return iterator;
10033 }
10034 } break;
10035 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
10036 Spannable text = (Spannable) getIterableTextForAccessibility();
10037 if (!TextUtils.isEmpty(text) && getLayout() != null) {
10038 AccessibilityIterators.PageTextSegmentIterator iterator =
10039 AccessibilityIterators.PageTextSegmentIterator.getInstance();
10040 iterator.initialize(this);
10041 return iterator;
10042 }
10043 } break;
10044 }
10045 return super.getIteratorForGranularity(granularity);
10046 }
10047
10048 /**
10049 * @hide
10050 */
10051 @Override
Svetoslav7c512842013-01-30 23:02:08 -080010052 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010053 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -080010054 }
10055
10056 /**
10057 * @hide
10058 */
10059 public boolean isAccessibilitySelectionExtendable() {
10060 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010061 }
10062
10063 /**
10064 * @hide
10065 */
10066 @Override
Svetoslav7c512842013-01-30 23:02:08 -080010067 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -070010068 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -080010069 }
10070
10071 /**
10072 * @hide
10073 */
10074 @Override
10075 public void setAccessibilitySelection(int start, int end) {
10076 if (getAccessibilitySelectionStart() == start
10077 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010078 return;
10079 }
Svetoslavabad55d2013-05-07 18:49:51 -070010080 // Hide all selection controllers used for adjusting selection
10081 // since we are doing so explicitlty by other means and these
10082 // controllers interact with how selection behaves.
10083 if (mEditor != null) {
Mady Mellora2861452015-06-25 08:40:27 -070010084 mEditor.hideCursorAndSpanControllers();
Mady Mellor011a42d2015-06-24 17:27:21 -070010085 mEditor.stopTextActionMode();
Svetoslavabad55d2013-05-07 18:49:51 -070010086 }
Svetoslav7c512842013-01-30 23:02:08 -080010087 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -070010088 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -080010089 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010090 } else {
Svetoslav7c512842013-01-30 23:02:08 -080010091 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010092 }
10093 }
10094
Siva Velusamy94a6d152015-05-05 15:07:00 -070010095 /** @hide */
10096 @Override
10097 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
10098 super.encodeProperties(stream);
10099
10100 TruncateAt ellipsize = getEllipsize();
10101 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
10102 stream.addProperty("text:textSize", getTextSize());
10103 stream.addProperty("text:scaledTextSize", getScaledTextSize());
10104 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
10105 stream.addProperty("text:selectionStart", getSelectionStart());
10106 stream.addProperty("text:selectionEnd", getSelectionEnd());
10107 stream.addProperty("text:curTextColor", mCurTextColor);
10108 stream.addProperty("text:text", mText == null ? null : mText.toString());
10109 stream.addProperty("text:gravity", mGravity);
10110 }
10111
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070010112 /**
Gilles Debunne60e21862012-01-30 15:04:14 -080010113 * User interface state that is stored by TextView for implementing
10114 * {@link View#onSaveInstanceState}.
10115 */
10116 public static class SavedState extends BaseSavedState {
Siyamed Sinir02771192016-02-05 16:08:59 -080010117 int selStart = -1;
10118 int selEnd = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -080010119 CharSequence text;
10120 boolean frozenWithFocus;
10121 CharSequence error;
James Cookf59152c2015-02-26 18:03:58 -080010122 ParcelableParcel editorState; // Optional state from Editor.
Gilles Debunne60e21862012-01-30 15:04:14 -080010123
10124 SavedState(Parcelable superState) {
10125 super(superState);
10126 }
10127
10128 @Override
10129 public void writeToParcel(Parcel out, int flags) {
10130 super.writeToParcel(out, flags);
10131 out.writeInt(selStart);
10132 out.writeInt(selEnd);
10133 out.writeInt(frozenWithFocus ? 1 : 0);
10134 TextUtils.writeToParcel(text, out, flags);
10135
10136 if (error == null) {
10137 out.writeInt(0);
10138 } else {
10139 out.writeInt(1);
10140 TextUtils.writeToParcel(error, out, flags);
10141 }
James Cookf59152c2015-02-26 18:03:58 -080010142
10143 if (editorState == null) {
10144 out.writeInt(0);
10145 } else {
10146 out.writeInt(1);
10147 editorState.writeToParcel(out, flags);
10148 }
Gilles Debunne60e21862012-01-30 15:04:14 -080010149 }
10150
10151 @Override
10152 public String toString() {
10153 String str = "TextView.SavedState{"
10154 + Integer.toHexString(System.identityHashCode(this))
10155 + " start=" + selStart + " end=" + selEnd;
10156 if (text != null) {
10157 str += " text=" + text;
10158 }
10159 return str + "}";
10160 }
10161
10162 @SuppressWarnings("hiding")
10163 public static final Parcelable.Creator<SavedState> CREATOR
10164 = new Parcelable.Creator<SavedState>() {
10165 public SavedState createFromParcel(Parcel in) {
10166 return new SavedState(in);
10167 }
10168
10169 public SavedState[] newArray(int size) {
10170 return new SavedState[size];
10171 }
10172 };
10173
10174 private SavedState(Parcel in) {
10175 super(in);
10176 selStart = in.readInt();
10177 selEnd = in.readInt();
10178 frozenWithFocus = (in.readInt() != 0);
10179 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10180
10181 if (in.readInt() != 0) {
10182 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
10183 }
James Cookf59152c2015-02-26 18:03:58 -080010184
10185 if (in.readInt() != 0) {
10186 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
10187 }
Gilles Debunne60e21862012-01-30 15:04:14 -080010188 }
10189 }
10190
10191 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
10192 private char[] mChars;
10193 private int mStart, mLength;
10194
10195 public CharWrapper(char[] chars, int start, int len) {
10196 mChars = chars;
10197 mStart = start;
10198 mLength = len;
10199 }
10200
10201 /* package */ void set(char[] chars, int start, int len) {
10202 mChars = chars;
10203 mStart = start;
10204 mLength = len;
10205 }
10206
10207 public int length() {
10208 return mLength;
10209 }
10210
10211 public char charAt(int off) {
10212 return mChars[off + mStart];
10213 }
10214
10215 @Override
10216 public String toString() {
10217 return new String(mChars, mStart, mLength);
10218 }
10219
10220 public CharSequence subSequence(int start, int end) {
10221 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10222 throw new IndexOutOfBoundsException(start + ", " + end);
10223 }
10224
10225 return new String(mChars, start + mStart, end - start);
10226 }
10227
10228 public void getChars(int start, int end, char[] buf, int off) {
10229 if (start < 0 || end < 0 || start > mLength || end > mLength) {
10230 throw new IndexOutOfBoundsException(start + ", " + end);
10231 }
10232
10233 System.arraycopy(mChars, start + mStart, buf, off, end - start);
10234 }
10235
10236 public void drawText(Canvas c, int start, int end,
10237 float x, float y, Paint p) {
10238 c.drawText(mChars, start + mStart, end - start, x, y, p);
10239 }
10240
10241 public void drawTextRun(Canvas c, int start, int end,
Raph Levien051910b2014-06-15 18:25:29 -070010242 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010243 int count = end - start;
10244 int contextCount = contextEnd - contextStart;
10245 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -070010246 contextCount, x, y, isRtl, p);
Gilles Debunne60e21862012-01-30 15:04:14 -080010247 }
10248
10249 public float measureText(int start, int end, Paint p) {
10250 return p.measureText(mChars, start + mStart, end - start);
10251 }
10252
10253 public int getTextWidths(int start, int end, float[] widths, Paint p) {
10254 return p.getTextWidths(mChars, start + mStart, end - start, widths);
10255 }
10256
10257 public float getTextRunAdvances(int start, int end, int contextStart,
Raph Levien051910b2014-06-15 18:25:29 -070010258 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -080010259 Paint p) {
10260 int count = end - start;
10261 int contextCount = contextEnd - contextStart;
10262 return p.getTextRunAdvances(mChars, start + mStart, count,
Raph Levien051910b2014-06-15 18:25:29 -070010263 contextStart + mStart, contextCount, isRtl, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -080010264 advancesIndex);
10265 }
10266
Raph Levien051910b2014-06-15 18:25:29 -070010267 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
Gilles Debunne60e21862012-01-30 15:04:14 -080010268 int offset, int cursorOpt, Paint p) {
10269 int contextCount = contextEnd - contextStart;
10270 return p.getTextRunCursor(mChars, contextStart + mStart,
Raph Levien051910b2014-06-15 18:25:29 -070010271 contextCount, dir, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -080010272 }
10273 }
10274
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010275 private static final class Marquee {
Gilles Debunne60e21862012-01-30 15:04:14 -080010276 // TODO: Add an option to configure this
10277 private static final float MARQUEE_DELTA_MAX = 0.07f;
10278 private static final int MARQUEE_DELAY = 1200;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010279 private static final int MARQUEE_DP_PER_SECOND = 30;
Gilles Debunne60e21862012-01-30 15:04:14 -080010280
10281 private static final byte MARQUEE_STOPPED = 0x0;
10282 private static final byte MARQUEE_STARTING = 0x1;
10283 private static final byte MARQUEE_RUNNING = 0x2;
10284
Gilles Debunne60e21862012-01-30 15:04:14 -080010285 private final WeakReference<TextView> mView;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010286 private final Choreographer mChoreographer;
Gilles Debunne60e21862012-01-30 15:04:14 -080010287
10288 private byte mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010289 private final float mPixelsPerSecond;
Gilles Debunne60e21862012-01-30 15:04:14 -080010290 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010291 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -080010292 private float mGhostStart;
10293 private float mGhostOffset;
10294 private float mFadeStop;
10295 private int mRepeatLimit;
10296
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010297 private float mScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010298 private long mLastAnimationMs;
Gilles Debunne60e21862012-01-30 15:04:14 -080010299
10300 Marquee(TextView v) {
10301 final float density = v.getContext().getResources().getDisplayMetrics().density;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010302 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
Gilles Debunne60e21862012-01-30 15:04:14 -080010303 mView = new WeakReference<TextView>(v);
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010304 mChoreographer = Choreographer.getInstance();
Gilles Debunne60e21862012-01-30 15:04:14 -080010305 }
10306
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010307 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10308 @Override
10309 public void doFrame(long frameTimeNanos) {
10310 tick();
Gilles Debunne60e21862012-01-30 15:04:14 -080010311 }
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010312 };
10313
10314 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10315 @Override
10316 public void doFrame(long frameTimeNanos) {
10317 mStatus = MARQUEE_RUNNING;
10318 mLastAnimationMs = mChoreographer.getFrameTime();
10319 tick();
10320 }
10321 };
10322
10323 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10324 @Override
10325 public void doFrame(long frameTimeNanos) {
10326 if (mStatus == MARQUEE_RUNNING) {
10327 if (mRepeatLimit >= 0) {
10328 mRepeatLimit--;
10329 }
10330 start(mRepeatLimit);
10331 }
10332 }
10333 };
Gilles Debunne60e21862012-01-30 15:04:14 -080010334
10335 void tick() {
10336 if (mStatus != MARQUEE_RUNNING) {
10337 return;
10338 }
10339
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010340 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010341
10342 final TextView textView = mView.get();
10343 if (textView != null && (textView.isFocused() || textView.isSelected())) {
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010344 long currentMs = mChoreographer.getFrameTime();
10345 long deltaMs = currentMs - mLastAnimationMs;
10346 mLastAnimationMs = currentMs;
10347 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10348 mScroll += deltaPx;
Gilles Debunne60e21862012-01-30 15:04:14 -080010349 if (mScroll > mMaxScroll) {
10350 mScroll = mMaxScroll;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010351 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
Gilles Debunne60e21862012-01-30 15:04:14 -080010352 } else {
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010353 mChoreographer.postFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010354 }
10355 textView.invalidate();
10356 }
10357 }
10358
10359 void stop() {
10360 mStatus = MARQUEE_STOPPED;
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010361 mChoreographer.removeFrameCallback(mStartCallback);
10362 mChoreographer.removeFrameCallback(mRestartCallback);
10363 mChoreographer.removeFrameCallback(mTickCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010364 resetScroll();
10365 }
10366
10367 private void resetScroll() {
10368 mScroll = 0.0f;
10369 final TextView textView = mView.get();
10370 if (textView != null) textView.invalidate();
10371 }
10372
10373 void start(int repeatLimit) {
10374 if (repeatLimit == 0) {
10375 stop();
10376 return;
10377 }
10378 mRepeatLimit = repeatLimit;
10379 final TextView textView = mView.get();
10380 if (textView != null && textView.mLayout != null) {
10381 mStatus = MARQUEE_STARTING;
10382 mScroll = 0.0f;
10383 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10384 textView.getCompoundPaddingRight();
10385 final float lineWidth = textView.mLayout.getLineWidth(0);
10386 final float gap = textWidth / 3.0f;
10387 mGhostStart = lineWidth - textWidth + gap;
10388 mMaxScroll = mGhostStart + textWidth;
10389 mGhostOffset = lineWidth + gap;
10390 mFadeStop = lineWidth + textWidth / 6.0f;
10391 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10392
10393 textView.invalidate();
Jorim Jaggi3aa42202014-07-01 14:59:34 +020010394 mChoreographer.postFrameCallback(mStartCallback);
Gilles Debunne60e21862012-01-30 15:04:14 -080010395 }
10396 }
10397
10398 float getGhostOffset() {
10399 return mGhostOffset;
10400 }
10401
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -070010402 float getScroll() {
10403 return mScroll;
10404 }
10405
10406 float getMaxFadeScroll() {
10407 return mMaxFadeScroll;
10408 }
10409
Gilles Debunne60e21862012-01-30 15:04:14 -080010410 boolean shouldDrawLeftFade() {
10411 return mScroll <= mFadeStop;
10412 }
10413
10414 boolean shouldDrawGhost() {
10415 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10416 }
10417
10418 boolean isRunning() {
10419 return mStatus == MARQUEE_RUNNING;
10420 }
10421
10422 boolean isStopped() {
10423 return mStatus == MARQUEE_STOPPED;
10424 }
10425 }
10426
Gilles Debunne60e21862012-01-30 15:04:14 -080010427 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10428
10429 private CharSequence mBeforeText;
10430
Gilles Debunne60e21862012-01-30 15:04:14 -080010431 public void beforeTextChanged(CharSequence buffer, int start,
10432 int before, int after) {
10433 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10434 + " before=" + before + " after=" + after + ": " + buffer);
10435
10436 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -080010437 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10438 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010439 mBeforeText = buffer.toString();
10440 }
10441
10442 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10443 }
10444
Gilles Debunned88876a2012-03-16 17:34:04 -070010445 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010446 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10447 + " before=" + before + " after=" + after + ": " + buffer);
10448 TextView.this.handleTextChanged(buffer, start, before, after);
10449
Gilles Debunne60e21862012-01-30 15:04:14 -080010450 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10451 (isFocused() || isSelected() && isShown())) {
10452 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10453 mBeforeText = null;
10454 }
10455 }
10456
10457 public void afterTextChanged(Editable buffer) {
10458 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10459 TextView.this.sendAfterTextChanged(buffer);
10460
10461 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10462 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10463 }
10464 }
10465
Gilles Debunned88876a2012-03-16 17:34:04 -070010466 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -080010467 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10468 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10469 TextView.this.spanChange(buf, what, s, st, e, en);
10470 }
10471
10472 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10473 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10474 + " what=" + what + ": " + buf);
10475 TextView.this.spanChange(buf, what, -1, s, -1, e);
10476 }
10477
10478 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10479 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10480 + " what=" + what + ": " + buf);
10481 TextView.this.spanChange(buf, what, s, -1, e, -1);
10482 }
satoka67a3cf2011-09-07 17:14:03 +090010483 }
Mady Mellor138bc2f2015-03-05 15:15:25 +000010484}