blob: 7a9809fda6059cc17fbbb6c1b7daf1419aa9196a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Gilles Debunne78996c92010-10-12 16:01:47 -070019import android.R;
Dianne Hackborn1040dc42010-08-26 22:11:06 -070020import android.content.ClipData;
Gilles Debunne64e54a62010-09-07 19:07:17 -070021import android.content.ClipboardManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.Context;
Dianne Hackborn3aa49b62013-04-26 16:39:17 -070023import android.content.UndoManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.ColorStateList;
Christopher Tate1373a8e2011-11-10 19:59:13 -080025import android.content.res.CompatibilityInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.res.Resources;
27import android.content.res.TypedArray;
28import android.content.res.XmlResourceParser;
29import android.graphics.Canvas;
Philip Milne7b757812012-09-19 18:13:44 -070030import android.graphics.Insets;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.graphics.Paint;
32import android.graphics.Path;
33import android.graphics.Rect;
34import android.graphics.RectF;
35import android.graphics.Typeface;
36import android.graphics.drawable.Drawable;
Gilles Debunne64e54a62010-09-07 19:07:17 -070037import android.inputmethodservice.ExtractEditText;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +090038import android.os.AsyncTask;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.os.Bundle;
40import android.os.Handler;
svetoslavganov75986cf2009-05-14 22:28:01 -070041import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.os.Parcel;
43import android.os.Parcelable;
44import android.os.SystemClock;
alanv7d624192012-05-21 14:23:17 -070045import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import android.text.BoringLayout;
47import android.text.DynamicLayout;
48import android.text.Editable;
49import android.text.GetChars;
50import android.text.GraphicsOperations;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.text.InputFilter;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -070052import android.text.InputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053import android.text.Layout;
54import android.text.ParcelableSpan;
55import android.text.Selection;
56import android.text.SpanWatcher;
57import android.text.Spannable;
svetoslavganov75986cf2009-05-14 22:28:01 -070058import android.text.SpannableString;
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -070059import android.text.SpannableStringBuilder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.text.Spanned;
61import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070063import android.text.TextDirectionHeuristic;
64import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import android.text.TextPaint;
66import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070067import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070068import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070069import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080070import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071import android.text.method.DateKeyListener;
72import android.text.method.DateTimeKeyListener;
73import android.text.method.DialerKeyListener;
74import android.text.method.DigitsKeyListener;
75import android.text.method.KeyListener;
76import android.text.method.LinkMovementMethod;
77import android.text.method.MetaKeyKeyListener;
78import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079import android.text.method.PasswordTransformationMethod;
80import android.text.method.SingleLineTransformationMethod;
81import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070082import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070084import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070085import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080086import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080087import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070089import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -070090import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091import android.text.style.URLSpan;
92import android.text.style.UpdateAppearance;
93import android.text.util.Linkify;
94import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070096import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070098import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -070099import android.view.ActionMode;
Gilles Debunnef170a342010-11-11 11:08:59 -0800100import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700102import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800103import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104import android.view.KeyEvent;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700105import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106import android.view.MenuItem;
107import android.view.MotionEvent;
108import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700109import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700111import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700112import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700114import android.view.accessibility.AccessibilityEvent;
115import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700116import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117import android.view.animation.AnimationUtils;
118import android.view.inputmethod.BaseInputConnection;
119import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800120import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700121import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122import android.view.inputmethod.ExtractedText;
123import android.view.inputmethod.ExtractedTextRequest;
124import android.view.inputmethod.InputConnection;
125import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900126import android.view.textservice.SpellCheckerSubtype;
127import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128import android.widget.RemoteViews.RemoteView;
129
Gilles Debunne22378292011-08-12 10:38:52 -0700130import com.android.internal.util.FastMath;
131import com.android.internal.widget.EditableInputConnection;
132
133import org.xmlpull.v1.XmlPullParserException;
134
Gilles Debunne27113f82010-08-23 12:09:14 -0700135import java.io.IOException;
136import java.lang.ref.WeakReference;
137import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700138import java.util.Locale;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900139import java.util.concurrent.locks.ReentrantLock;
Gilles Debunne27113f82010-08-23 12:09:14 -0700140
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700141import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
142
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143/**
144 * Displays text to the user and optionally allows them to edit it. A TextView
145 * is a complete text editor, however the basic class is configured to not
146 * allow editing; see {@link EditText} for a subclass that configures the text
147 * view for editing.
148 *
149 * <p>
Joe Malin10d96952013-05-29 17:49:09 -0700150 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
151 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
152 * android:textIsSelectable} to "true" or call
153 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
154 * allows users to make selection gestures in the TextView, which in turn triggers the system's
155 * built-in copy/paste controls.
156 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 * <b>XML attributes</b>
158 * <p>
159 * See {@link android.R.styleable#TextView TextView Attributes},
160 * {@link android.R.styleable#View View Attributes}
161 *
162 * @attr ref android.R.styleable#TextView_text
163 * @attr ref android.R.styleable#TextView_bufferType
164 * @attr ref android.R.styleable#TextView_hint
165 * @attr ref android.R.styleable#TextView_textColor
166 * @attr ref android.R.styleable#TextView_textColorHighlight
167 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700168 * @attr ref android.R.styleable#TextView_textAppearance
169 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 * @attr ref android.R.styleable#TextView_textSize
171 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700172 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 * @attr ref android.R.styleable#TextView_typeface
174 * @attr ref android.R.styleable#TextView_textStyle
175 * @attr ref android.R.styleable#TextView_cursorVisible
176 * @attr ref android.R.styleable#TextView_maxLines
177 * @attr ref android.R.styleable#TextView_maxHeight
178 * @attr ref android.R.styleable#TextView_lines
179 * @attr ref android.R.styleable#TextView_height
180 * @attr ref android.R.styleable#TextView_minLines
181 * @attr ref android.R.styleable#TextView_minHeight
182 * @attr ref android.R.styleable#TextView_maxEms
183 * @attr ref android.R.styleable#TextView_maxWidth
184 * @attr ref android.R.styleable#TextView_ems
185 * @attr ref android.R.styleable#TextView_width
186 * @attr ref android.R.styleable#TextView_minEms
187 * @attr ref android.R.styleable#TextView_minWidth
188 * @attr ref android.R.styleable#TextView_gravity
189 * @attr ref android.R.styleable#TextView_scrollHorizontally
190 * @attr ref android.R.styleable#TextView_password
191 * @attr ref android.R.styleable#TextView_singleLine
192 * @attr ref android.R.styleable#TextView_selectAllOnFocus
193 * @attr ref android.R.styleable#TextView_includeFontPadding
194 * @attr ref android.R.styleable#TextView_maxLength
195 * @attr ref android.R.styleable#TextView_shadowColor
196 * @attr ref android.R.styleable#TextView_shadowDx
197 * @attr ref android.R.styleable#TextView_shadowDy
198 * @attr ref android.R.styleable#TextView_shadowRadius
199 * @attr ref android.R.styleable#TextView_autoLink
200 * @attr ref android.R.styleable#TextView_linksClickable
201 * @attr ref android.R.styleable#TextView_numeric
202 * @attr ref android.R.styleable#TextView_digits
203 * @attr ref android.R.styleable#TextView_phoneNumber
204 * @attr ref android.R.styleable#TextView_inputMethod
205 * @attr ref android.R.styleable#TextView_capitalize
206 * @attr ref android.R.styleable#TextView_autoText
207 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700208 * @attr ref android.R.styleable#TextView_freezesText
209 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 * @attr ref android.R.styleable#TextView_drawableTop
211 * @attr ref android.R.styleable#TextView_drawableBottom
212 * @attr ref android.R.styleable#TextView_drawableRight
213 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800214 * @attr ref android.R.styleable#TextView_drawableStart
215 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700216 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 * @attr ref android.R.styleable#TextView_lineSpacingExtra
218 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
219 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700220 * @attr ref android.R.styleable#TextView_inputType
221 * @attr ref android.R.styleable#TextView_imeOptions
222 * @attr ref android.R.styleable#TextView_privateImeOptions
223 * @attr ref android.R.styleable#TextView_imeActionLabel
224 * @attr ref android.R.styleable#TextView_imeActionId
225 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 */
227@RemoteView
228public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700229 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 // Enum for the "typeface" XML parameter.
233 // TODO: How can we get this from the XML instead of hardcoding it here?
234 private static final int SANS = 1;
235 private static final int SERIF = 2;
236 private static final int MONOSPACE = 3;
237
238 // Bitfield for the "numeric" XML parameter.
239 // TODO: How can we get this from the XML instead of hardcoding it here?
240 private static final int SIGNED = 2;
241 private static final int DECIMAL = 4;
242
Adam Powell282e3772011-08-30 16:51:11 -0700243 /**
244 * Draw marquee text with fading edges as usual
245 */
246 private static final int MARQUEE_FADE_NORMAL = 0;
247
248 /**
249 * Draw marquee text as ellipsize end while inactive instead of with the fade.
250 * (Useful for devices where the fade can be expensive if overdone)
251 */
252 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
253
254 /**
255 * Draw marquee text with fading edges because it is currently active/animating.
256 */
257 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
258
Gilles Debunne60e21862012-01-30 15:04:14 -0800259 private static final int LINES = 1;
260 private static final int EMS = LINES;
261 private static final int PIXELS = 2;
262
263 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800264
265 // XXX should be much larger
266 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800267 private static final int ANIMATED_SCROLL_GAP = 250;
268
269 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
270 private static final Spanned EMPTY_SPANNED = new SpannedString("");
271
Gilles Debunne60e21862012-01-30 15:04:14 -0800272 private static final int CHANGE_WATCHER_PRIORITY = 100;
273
274 // New state used to change background based on whether this TextView is multiline.
275 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
276
277 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700278 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800279
Gilles Debunne60e21862012-01-30 15:04:14 -0800280 private ColorStateList mTextColor;
281 private ColorStateList mHintTextColor;
282 private ColorStateList mLinkTextColor;
283 private int mCurTextColor;
284 private int mCurHintTextColor;
285 private boolean mFreezesText;
286 private boolean mTemporaryDetach;
287 private boolean mDispatchTemporaryDetach;
288
289 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
290 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
291
292 private float mShadowRadius, mShadowDx, mShadowDy;
293
294 private boolean mPreDrawRegistered;
295
Michael Wright3a7e4832013-02-11 15:55:50 -0800296 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
297 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
298 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
299 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
300 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
301 private boolean mPreventDefaultMovement;
302
Gilles Debunne60e21862012-01-30 15:04:14 -0800303 private TextUtils.TruncateAt mEllipsize;
304
305 static class Drawables {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800306 final static int DRAWABLE_NONE = -1;
307 final static int DRAWABLE_RIGHT = 0;
308 final static int DRAWABLE_LEFT = 1;
309
Gilles Debunne60e21862012-01-30 15:04:14 -0800310 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800311
Gilles Debunne60e21862012-01-30 15:04:14 -0800312 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800313 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
314
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700315 Drawable mDrawableLeftInitial, mDrawableRightInitial;
316 boolean mIsRtlCompatibilityMode;
317 boolean mOverride;
318
Gilles Debunne60e21862012-01-30 15:04:14 -0800319 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800320 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
321
Gilles Debunne60e21862012-01-30 15:04:14 -0800322 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800323 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
324
Gilles Debunne60e21862012-01-30 15:04:14 -0800325 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800326
327 int mDrawableSaved = DRAWABLE_NONE;
328
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700329 public Drawables(Context context) {
330 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
331 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
332 !context.getApplicationInfo().hasRtlSupport());
333 mOverride = false;
334 }
335
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800336 public void resolveWithLayoutDirection(int layoutDirection) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700337 // First reset "left" and "right" drawables to their initial values
338 mDrawableLeft = mDrawableLeftInitial;
339 mDrawableRight = mDrawableRightInitial;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800340
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700341 if (mIsRtlCompatibilityMode) {
342 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
343 if (mDrawableStart != null && mDrawableLeft == null) {
344 mDrawableLeft = mDrawableStart;
345 mDrawableSizeLeft = mDrawableSizeStart;
346 mDrawableHeightLeft = mDrawableHeightStart;
347 }
348 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
349 if (mDrawableEnd != null && mDrawableRight == null) {
350 mDrawableRight = mDrawableEnd;
351 mDrawableSizeRight = mDrawableSizeEnd;
352 mDrawableHeightRight = mDrawableHeightEnd;
353 }
354 } else {
355 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
356 // drawable if and only if they have been defined
357 switch(layoutDirection) {
358 case LAYOUT_DIRECTION_RTL:
359 if (mOverride) {
360 mDrawableRight = mDrawableStart;
361 mDrawableSizeRight = mDrawableSizeStart;
362 mDrawableHeightRight = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800363
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700364 mDrawableLeft = mDrawableEnd;
365 mDrawableSizeLeft = mDrawableSizeEnd;
366 mDrawableHeightLeft = mDrawableHeightEnd;
367 }
368 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800369
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700370 case LAYOUT_DIRECTION_LTR:
371 default:
372 if (mOverride) {
373 mDrawableLeft = mDrawableStart;
374 mDrawableSizeLeft = mDrawableSizeStart;
375 mDrawableHeightLeft = mDrawableHeightStart;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800376
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700377 mDrawableRight = mDrawableEnd;
378 mDrawableSizeRight = mDrawableSizeEnd;
379 mDrawableHeightRight = mDrawableHeightEnd;
380 }
381 break;
382 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800383 }
384 applyErrorDrawableIfNeeded(layoutDirection);
385 updateDrawablesLayoutDirection(layoutDirection);
386 }
387
388 private void updateDrawablesLayoutDirection(int layoutDirection) {
389 if (mDrawableLeft != null) {
390 mDrawableLeft.setLayoutDirection(layoutDirection);
391 }
392 if (mDrawableRight != null) {
393 mDrawableRight.setLayoutDirection(layoutDirection);
394 }
395 if (mDrawableTop != null) {
396 mDrawableTop.setLayoutDirection(layoutDirection);
397 }
398 if (mDrawableBottom != null) {
399 mDrawableBottom.setLayoutDirection(layoutDirection);
400 }
401 }
402
403 public void setErrorDrawable(Drawable dr, TextView tv) {
404 if (mDrawableError != dr && mDrawableError != null) {
405 mDrawableError.setCallback(null);
406 }
407 mDrawableError = dr;
408
409 final Rect compoundRect = mCompoundRect;
410 int[] state = tv.getDrawableState();
411
412 if (mDrawableError != null) {
413 mDrawableError.setState(state);
414 mDrawableError.copyBounds(compoundRect);
415 mDrawableError.setCallback(tv);
416 mDrawableSizeError = compoundRect.width();
417 mDrawableHeightError = compoundRect.height();
418 } else {
419 mDrawableSizeError = mDrawableHeightError = 0;
420 }
421 }
422
423 private void applyErrorDrawableIfNeeded(int layoutDirection) {
424 // first restore the initial state if needed
425 switch (mDrawableSaved) {
426 case DRAWABLE_LEFT:
427 mDrawableLeft = mDrawableTemp;
428 mDrawableSizeLeft = mDrawableSizeTemp;
429 mDrawableHeightLeft = mDrawableHeightTemp;
430 break;
431 case DRAWABLE_RIGHT:
432 mDrawableRight = mDrawableTemp;
433 mDrawableSizeRight = mDrawableSizeTemp;
434 mDrawableHeightRight = mDrawableHeightTemp;
435 break;
436 case DRAWABLE_NONE:
437 default:
438 }
439 // then, if needed, assign the Error drawable to the correct location
440 if (mDrawableError != null) {
441 switch(layoutDirection) {
442 case LAYOUT_DIRECTION_RTL:
443 mDrawableSaved = DRAWABLE_LEFT;
444
445 mDrawableTemp = mDrawableLeft;
446 mDrawableSizeTemp = mDrawableSizeLeft;
447 mDrawableHeightTemp = mDrawableHeightLeft;
448
449 mDrawableLeft = mDrawableError;
450 mDrawableSizeLeft = mDrawableSizeError;
451 mDrawableHeightLeft = mDrawableHeightError;
452 break;
453 case LAYOUT_DIRECTION_LTR:
454 default:
455 mDrawableSaved = DRAWABLE_RIGHT;
456
457 mDrawableTemp = mDrawableRight;
458 mDrawableSizeTemp = mDrawableSizeRight;
459 mDrawableHeightTemp = mDrawableHeightRight;
460
461 mDrawableRight = mDrawableError;
462 mDrawableSizeRight = mDrawableSizeError;
463 mDrawableHeightRight = mDrawableHeightError;
464 break;
465 }
466 }
467 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800468 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800469
Gilles Debunned88876a2012-03-16 17:34:04 -0700470 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800471
472 private CharWrapper mCharWrapper;
473
474 private Marquee mMarquee;
475 private boolean mRestartMarquee;
476
477 private int mMarqueeRepeatLimit = 3;
478
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700479 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800480
481 /**
482 * On some devices the fading edges add a performance penalty if used
483 * extensively in the same layout. This mode indicates how the marquee
484 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
485 */
486 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
487
488 /**
489 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
490 * the layout that should be used when the mode switches.
491 */
492 private Layout mSavedMarqueeModeLayout;
493
494 @ViewDebug.ExportedProperty(category = "text")
495 private CharSequence mText;
496 private CharSequence mTransformed;
497 private BufferType mBufferType = BufferType.NORMAL;
498
499 private CharSequence mHint;
500 private Layout mHintLayout;
501
502 private MovementMethod mMovement;
503
504 private TransformationMethod mTransformation;
505 private boolean mAllowTransformationLengthChange;
506 private ChangeWatcher mChangeWatcher;
507
508 private ArrayList<TextWatcher> mListeners;
509
510 // display attributes
511 private final TextPaint mTextPaint;
512 private boolean mUserSetTextScaleX;
513 private Layout mLayout;
514
515 private int mGravity = Gravity.TOP | Gravity.START;
516 private boolean mHorizontallyScrolling;
517
518 private int mAutoLinkMask;
519 private boolean mLinksClickable = true;
520
521 private float mSpacingMult = 1.0f;
522 private float mSpacingAdd = 0.0f;
523
524 private int mMaximum = Integer.MAX_VALUE;
525 private int mMaxMode = LINES;
526 private int mMinimum = 0;
527 private int mMinMode = LINES;
528
529 private int mOldMaximum = mMaximum;
530 private int mOldMaxMode = mMaxMode;
531
532 private int mMaxWidth = Integer.MAX_VALUE;
533 private int mMaxWidthMode = PIXELS;
534 private int mMinWidth = 0;
535 private int mMinWidthMode = PIXELS;
536
537 private boolean mSingleLine;
538 private int mDesiredHeightAtMeasure = -1;
539 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700540 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800541
542 // tmp primitives, so we don't alloc them on each draw
543 private Rect mTempRect;
544 private long mLastScroll;
545 private Scroller mScroller;
546
547 private BoringLayout.Metrics mBoring, mHintBoring;
548 private BoringLayout mSavedLayout, mSavedHintLayout;
549
550 private TextDirectionHeuristic mTextDir;
551
552 private InputFilter[] mFilters = NO_FILTERS;
553
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900554 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900555
Gilles Debunne83051b82012-02-24 20:01:13 -0800556 // It is possible to have a selection even when mEditor is null (programmatically set, like when
557 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700558 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800559 private Path mHighlightPath;
560 private final Paint mHighlightPaint;
561 private boolean mHighlightPathBogus = true;
562
Gilles Debunne60e21862012-01-30 15:04:14 -0800563 // Although these fields are specific to editable text, they are not added to Editor because
564 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700565 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800566 // These four fields, could be moved to Editor, since we know their default values and we
567 // could condition the creation of the Editor to a non standard value. This is however
568 // brittle since the hardcoded values here (such as
569 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
570 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700571 int mTextSelectHandleLeftRes;
572 int mTextSelectHandleRightRes;
573 int mTextSelectHandleRes;
574 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800575
576 /**
577 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700578 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800579 */
580 private Editor mEditor;
581
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800582 /*
583 * Kick-start the font cache for the zygote process (to pay the cost of
584 * initializing freetype for our default font only once).
585 */
586 static {
587 Paint p = new Paint();
588 p.setAntiAlias(true);
589 // We don't care about the result, just the side-effect of measuring.
590 p.measureText("H");
591 }
592
593 /**
594 * Interface definition for a callback to be invoked when an action is
595 * performed on the editor.
596 */
597 public interface OnEditorActionListener {
598 /**
599 * Called when an action is being performed.
600 *
601 * @param v The view that was clicked.
602 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700603 * identifier you supplied, or {@link EditorInfo#IME_NULL
604 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 * being pressed.
606 * @param event If triggered by an enter key, this is the event;
607 * otherwise, this is null.
608 * @return Return true if you have consumed the action, else false.
609 */
610 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
611 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700612
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 public TextView(Context context) {
614 this(context, null);
615 }
616
Gilles Debunnec1714022012-01-17 13:59:23 -0800617 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 this(context, attrs, com.android.internal.R.attr.textViewStyle);
619 }
620
Gilles Debunnee15b3582010-06-16 15:17:21 -0700621 @SuppressWarnings("deprecation")
Gilles Debunnec1714022012-01-17 13:59:23 -0800622 public TextView(Context context, AttributeSet attrs, int defStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800623 super(context, attrs, defStyle);
624 mText = "";
625
Christopher Tate1373a8e2011-11-10 19:59:13 -0800626 final Resources res = getResources();
627 final CompatibilityInfo compat = res.getCompatibilityInfo();
628
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800630 mTextPaint.density = res.getDisplayMetrics().density;
631 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800632
Gilles Debunne83051b82012-02-24 20:01:13 -0800633 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
634 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
635
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800636 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800637
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 mTransformation = null;
639
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 int textColorHighlight = 0;
641 ColorStateList textColor = null;
642 ColorStateList textColorHint = null;
643 ColorStateList textColorLink = null;
644 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700645 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 int typefaceIndex = -1;
647 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700648 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800649 int shadowcolor = 0;
650 float dx = 0, dy = 0, r = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700652 final Resources.Theme theme = context.getTheme();
653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 /*
655 * Look the appearance up without checking first if it exists because
656 * almost every TextView has one and it greatly simplifies the logic
657 * to be able to parse the appearance first and then let specific tags
658 * for this View override it.
659 */
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700660 TypedArray a = theme.obtainStyledAttributes(
661 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800662 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700663 int ap = a.getResourceId(
664 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
665 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700667 appearance = theme.obtainStyledAttributes(
668 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 }
670 if (appearance != null) {
671 int n = appearance.getIndexCount();
672 for (int i = 0; i < n; i++) {
673 int attr = appearance.getIndex(i);
674
675 switch (attr) {
676 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
677 textColorHighlight = appearance.getColor(attr, textColorHighlight);
678 break;
679
680 case com.android.internal.R.styleable.TextAppearance_textColor:
681 textColor = appearance.getColorStateList(attr);
682 break;
683
684 case com.android.internal.R.styleable.TextAppearance_textColorHint:
685 textColorHint = appearance.getColorStateList(attr);
686 break;
687
688 case com.android.internal.R.styleable.TextAppearance_textColorLink:
689 textColorLink = appearance.getColorStateList(attr);
690 break;
691
692 case com.android.internal.R.styleable.TextAppearance_textSize:
693 textSize = appearance.getDimensionPixelSize(attr, textSize);
694 break;
695
696 case com.android.internal.R.styleable.TextAppearance_typeface:
697 typefaceIndex = appearance.getInt(attr, -1);
698 break;
699
Raph Leviend570e892012-05-09 11:45:34 -0700700 case com.android.internal.R.styleable.TextAppearance_fontFamily:
701 fontFamily = appearance.getString(attr);
702 break;
703
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 case com.android.internal.R.styleable.TextAppearance_textStyle:
705 styleIndex = appearance.getInt(attr, -1);
706 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700707
708 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
709 allCaps = appearance.getBoolean(attr, false);
710 break;
Adam Powellac91df82013-02-14 13:48:47 -0800711
712 case com.android.internal.R.styleable.TextAppearance_shadowColor:
713 shadowcolor = a.getInt(attr, 0);
714 break;
715
716 case com.android.internal.R.styleable.TextAppearance_shadowDx:
717 dx = a.getFloat(attr, 0);
718 break;
719
720 case com.android.internal.R.styleable.TextAppearance_shadowDy:
721 dy = a.getFloat(attr, 0);
722 break;
723
724 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
725 r = a.getFloat(attr, 0);
726 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 }
728 }
729
730 appearance.recycle();
731 }
732
733 boolean editable = getDefaultEditable();
734 CharSequence inputMethod = null;
735 int numeric = 0;
736 CharSequence digits = null;
737 boolean phone = false;
738 boolean autotext = false;
739 int autocap = -1;
740 int buffertype = 0;
741 boolean selectallonfocus = false;
742 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700743 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 int drawablePadding = 0;
745 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700746 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 int maxlength = -1;
748 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700749 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 boolean password = false;
751 int inputType = EditorInfo.TYPE_NULL;
752
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700753 a = theme.obtainStyledAttributes(
754 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
755
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 int n = a.getIndexCount();
757 for (int i = 0; i < n; i++) {
758 int attr = a.getIndex(i);
759
760 switch (attr) {
761 case com.android.internal.R.styleable.TextView_editable:
762 editable = a.getBoolean(attr, editable);
763 break;
764
765 case com.android.internal.R.styleable.TextView_inputMethod:
766 inputMethod = a.getText(attr);
767 break;
768
769 case com.android.internal.R.styleable.TextView_numeric:
770 numeric = a.getInt(attr, numeric);
771 break;
772
773 case com.android.internal.R.styleable.TextView_digits:
774 digits = a.getText(attr);
775 break;
776
777 case com.android.internal.R.styleable.TextView_phoneNumber:
778 phone = a.getBoolean(attr, phone);
779 break;
780
781 case com.android.internal.R.styleable.TextView_autoText:
782 autotext = a.getBoolean(attr, autotext);
783 break;
784
785 case com.android.internal.R.styleable.TextView_capitalize:
786 autocap = a.getInt(attr, autocap);
787 break;
788
789 case com.android.internal.R.styleable.TextView_bufferType:
790 buffertype = a.getInt(attr, buffertype);
791 break;
792
793 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
794 selectallonfocus = a.getBoolean(attr, selectallonfocus);
795 break;
796
797 case com.android.internal.R.styleable.TextView_autoLink:
798 mAutoLinkMask = a.getInt(attr, 0);
799 break;
800
801 case com.android.internal.R.styleable.TextView_linksClickable:
802 mLinksClickable = a.getBoolean(attr, true);
803 break;
804
805 case com.android.internal.R.styleable.TextView_drawableLeft:
806 drawableLeft = a.getDrawable(attr);
807 break;
808
809 case com.android.internal.R.styleable.TextView_drawableTop:
810 drawableTop = a.getDrawable(attr);
811 break;
812
813 case com.android.internal.R.styleable.TextView_drawableRight:
814 drawableRight = a.getDrawable(attr);
815 break;
816
817 case com.android.internal.R.styleable.TextView_drawableBottom:
818 drawableBottom = a.getDrawable(attr);
819 break;
820
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700821 case com.android.internal.R.styleable.TextView_drawableStart:
822 drawableStart = a.getDrawable(attr);
823 break;
824
825 case com.android.internal.R.styleable.TextView_drawableEnd:
826 drawableEnd = a.getDrawable(attr);
827 break;
828
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 case com.android.internal.R.styleable.TextView_drawablePadding:
830 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
831 break;
832
833 case com.android.internal.R.styleable.TextView_maxLines:
834 setMaxLines(a.getInt(attr, -1));
835 break;
836
837 case com.android.internal.R.styleable.TextView_maxHeight:
838 setMaxHeight(a.getDimensionPixelSize(attr, -1));
839 break;
840
841 case com.android.internal.R.styleable.TextView_lines:
842 setLines(a.getInt(attr, -1));
843 break;
844
845 case com.android.internal.R.styleable.TextView_height:
846 setHeight(a.getDimensionPixelSize(attr, -1));
847 break;
848
849 case com.android.internal.R.styleable.TextView_minLines:
850 setMinLines(a.getInt(attr, -1));
851 break;
852
853 case com.android.internal.R.styleable.TextView_minHeight:
854 setMinHeight(a.getDimensionPixelSize(attr, -1));
855 break;
856
857 case com.android.internal.R.styleable.TextView_maxEms:
858 setMaxEms(a.getInt(attr, -1));
859 break;
860
861 case com.android.internal.R.styleable.TextView_maxWidth:
862 setMaxWidth(a.getDimensionPixelSize(attr, -1));
863 break;
864
865 case com.android.internal.R.styleable.TextView_ems:
866 setEms(a.getInt(attr, -1));
867 break;
868
869 case com.android.internal.R.styleable.TextView_width:
870 setWidth(a.getDimensionPixelSize(attr, -1));
871 break;
872
873 case com.android.internal.R.styleable.TextView_minEms:
874 setMinEms(a.getInt(attr, -1));
875 break;
876
877 case com.android.internal.R.styleable.TextView_minWidth:
878 setMinWidth(a.getDimensionPixelSize(attr, -1));
879 break;
880
881 case com.android.internal.R.styleable.TextView_gravity:
882 setGravity(a.getInt(attr, -1));
883 break;
884
885 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700886 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800887 break;
888
889 case com.android.internal.R.styleable.TextView_text:
890 text = a.getText(attr);
891 break;
892
893 case com.android.internal.R.styleable.TextView_scrollHorizontally:
894 if (a.getBoolean(attr, false)) {
895 setHorizontallyScrolling(true);
896 }
897 break;
898
899 case com.android.internal.R.styleable.TextView_singleLine:
900 singleLine = a.getBoolean(attr, singleLine);
901 break;
902
903 case com.android.internal.R.styleable.TextView_ellipsize:
904 ellipsize = a.getInt(attr, ellipsize);
905 break;
906
907 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
908 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
909 break;
910
911 case com.android.internal.R.styleable.TextView_includeFontPadding:
912 if (!a.getBoolean(attr, true)) {
913 setIncludeFontPadding(false);
914 }
915 break;
916
917 case com.android.internal.R.styleable.TextView_cursorVisible:
918 if (!a.getBoolean(attr, true)) {
919 setCursorVisible(false);
920 }
921 break;
922
923 case com.android.internal.R.styleable.TextView_maxLength:
924 maxlength = a.getInt(attr, -1);
925 break;
926
927 case com.android.internal.R.styleable.TextView_textScaleX:
928 setTextScaleX(a.getFloat(attr, 1.0f));
929 break;
930
931 case com.android.internal.R.styleable.TextView_freezesText:
932 mFreezesText = a.getBoolean(attr, false);
933 break;
934
935 case com.android.internal.R.styleable.TextView_shadowColor:
936 shadowcolor = a.getInt(attr, 0);
937 break;
938
939 case com.android.internal.R.styleable.TextView_shadowDx:
940 dx = a.getFloat(attr, 0);
941 break;
942
943 case com.android.internal.R.styleable.TextView_shadowDy:
944 dy = a.getFloat(attr, 0);
945 break;
946
947 case com.android.internal.R.styleable.TextView_shadowRadius:
948 r = a.getFloat(attr, 0);
949 break;
950
951 case com.android.internal.R.styleable.TextView_enabled:
952 setEnabled(a.getBoolean(attr, isEnabled()));
953 break;
954
955 case com.android.internal.R.styleable.TextView_textColorHighlight:
956 textColorHighlight = a.getColor(attr, textColorHighlight);
957 break;
958
959 case com.android.internal.R.styleable.TextView_textColor:
960 textColor = a.getColorStateList(attr);
961 break;
962
963 case com.android.internal.R.styleable.TextView_textColorHint:
964 textColorHint = a.getColorStateList(attr);
965 break;
966
967 case com.android.internal.R.styleable.TextView_textColorLink:
968 textColorLink = a.getColorStateList(attr);
969 break;
970
971 case com.android.internal.R.styleable.TextView_textSize:
972 textSize = a.getDimensionPixelSize(attr, textSize);
973 break;
974
975 case com.android.internal.R.styleable.TextView_typeface:
976 typefaceIndex = a.getInt(attr, typefaceIndex);
977 break;
978
979 case com.android.internal.R.styleable.TextView_textStyle:
980 styleIndex = a.getInt(attr, styleIndex);
981 break;
982
Raph Leviend570e892012-05-09 11:45:34 -0700983 case com.android.internal.R.styleable.TextView_fontFamily:
984 fontFamily = a.getString(attr);
985 break;
986
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 case com.android.internal.R.styleable.TextView_password:
988 password = a.getBoolean(attr, password);
989 break;
990
991 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
992 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
993 break;
994
995 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
996 mSpacingMult = a.getFloat(attr, mSpacingMult);
997 break;
998
999 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001000 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001 break;
1002
1003 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001004 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001005 mEditor.createInputContentTypeIfNeeded();
1006 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1007 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001008 break;
1009
1010 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001011 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001012 mEditor.createInputContentTypeIfNeeded();
1013 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014 break;
1015
1016 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001017 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001018 mEditor.createInputContentTypeIfNeeded();
1019 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1020 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 break;
1022
1023 case com.android.internal.R.styleable.TextView_privateImeOptions:
1024 setPrivateImeOptions(a.getString(attr));
1025 break;
1026
1027 case com.android.internal.R.styleable.TextView_editorExtras:
1028 try {
1029 setInputExtras(a.getResourceId(attr, 0));
1030 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001031 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001032 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001033 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 }
1035 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001036
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001037 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1038 mCursorDrawableRes = a.getResourceId(attr, 0);
1039 break;
1040
Adam Powellb08013c2010-09-16 16:28:11 -07001041 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1042 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1043 break;
1044
1045 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1046 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1047 break;
1048
1049 case com.android.internal.R.styleable.TextView_textSelectHandle:
1050 mTextSelectHandleRes = a.getResourceId(attr, 0);
1051 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001052
Gilles Debunne69340442011-03-31 13:37:51 -07001053 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1054 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1055 break;
1056
Gilles Debunne86b9c782010-11-11 10:43:48 -08001057 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001058 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001059 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001060
Adam Powell7f8f79a2011-07-07 18:35:54 -07001061 case com.android.internal.R.styleable.TextView_textAllCaps:
1062 allCaps = a.getBoolean(attr, false);
1063 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001064 }
1065 }
1066 a.recycle();
1067
1068 BufferType bufferType = BufferType.EDITABLE;
1069
Gilles Debunned7483bf2010-11-10 10:47:45 -08001070 final int variation =
1071 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1072 final boolean passwordInputType = variation
1073 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1074 final boolean webPasswordInputType = variation
1075 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001076 final boolean numberPasswordInputType = variation
1077 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001078
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001079 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001080 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081
1082 try {
1083 c = Class.forName(inputMethod.toString());
1084 } catch (ClassNotFoundException ex) {
1085 throw new RuntimeException(ex);
1086 }
1087
1088 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001089 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001090 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 } catch (InstantiationException ex) {
1092 throw new RuntimeException(ex);
1093 } catch (IllegalAccessException ex) {
1094 throw new RuntimeException(ex);
1095 }
1096 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001097 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001098 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001099 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001101 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001102 }
1103 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001104 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001105 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001106 // If no input type was specified, we will default to generic
1107 // text, since we can't tell the IME about the set of digits
1108 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001109 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001110 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001111 } else if (inputType != EditorInfo.TYPE_NULL) {
1112 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001113 // If set, the input type overrides what was set using the deprecated singleLine flag.
1114 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001115 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001116 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001117 mEditor.mKeyListener = DialerKeyListener.getInstance();
1118 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001119 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001120 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001121 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 (numeric & DECIMAL) != 0);
1123 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1124 if ((numeric & SIGNED) != 0) {
1125 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1126 }
1127 if ((numeric & DECIMAL) != 0) {
1128 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1129 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001130 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001131 } else if (autotext || autocap != -1) {
1132 TextKeyListener.Capitalize cap;
1133
1134 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001136 switch (autocap) {
1137 case 1:
1138 cap = TextKeyListener.Capitalize.SENTENCES;
1139 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1140 break;
1141
1142 case 2:
1143 cap = TextKeyListener.Capitalize.WORDS;
1144 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1145 break;
1146
1147 case 3:
1148 cap = TextKeyListener.Capitalize.CHARACTERS;
1149 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1150 break;
1151
1152 default:
1153 cap = TextKeyListener.Capitalize.NONE;
1154 break;
1155 }
1156
Gilles Debunne5fae9962012-05-08 14:53:20 -07001157 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001158 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1159 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001160 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001161 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001162 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001163 mEditor.mKeyListener = null;
1164 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001165 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001166 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001167 // So that selection can be changed using arrow keys and touch is handled.
1168 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001169 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001170 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001171 mEditor.mKeyListener = TextKeyListener.getInstance();
1172 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001174 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001175
1176 switch (buffertype) {
1177 case 0:
1178 bufferType = BufferType.NORMAL;
1179 break;
1180 case 1:
1181 bufferType = BufferType.SPANNABLE;
1182 break;
1183 case 2:
1184 bufferType = BufferType.EDITABLE;
1185 break;
1186 }
1187 }
1188
Gilles Debunne2d373a12012-04-20 15:32:19 -07001189 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1190 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001191
1192 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001193 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001194 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001195
1196 if (bufferType == BufferType.NORMAL)
1197 bufferType = BufferType.SPANNABLE;
1198 }
1199
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001200 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 setCompoundDrawablesWithIntrinsicBounds(
1202 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001203 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 setCompoundDrawablePadding(drawablePadding);
1205
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001206 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001207 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001208 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001209 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001210
Gilles Debunne60e21862012-01-30 15:04:14 -08001211 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001212 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
1214
1215 switch (ellipsize) {
1216 case 1:
1217 setEllipsize(TextUtils.TruncateAt.START);
1218 break;
1219 case 2:
1220 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1221 break;
1222 case 3:
1223 setEllipsize(TextUtils.TruncateAt.END);
1224 break;
1225 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001226 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1227 setHorizontalFadingEdgeEnabled(true);
1228 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1229 } else {
1230 setHorizontalFadingEdgeEnabled(false);
1231 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1234 break;
1235 }
1236
1237 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1238 setHintTextColor(textColorHint);
1239 setLinkTextColor(textColorLink);
1240 if (textColorHighlight != 0) {
1241 setHighlightColor(textColorHighlight);
1242 }
1243 setRawTextSize(textSize);
1244
Adam Powell7f8f79a2011-07-07 18:35:54 -07001245 if (allCaps) {
1246 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1247 }
1248
Ken Wakasa82d731a2010-12-24 23:42:41 +09001249 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 setTransformationMethod(PasswordTransformationMethod.getInstance());
1251 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001252 } else if (mEditor != null &&
1253 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001254 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001255 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001256 }
1257
Raph Leviend570e892012-05-09 11:45:34 -07001258 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259
1260 if (shadowcolor != 0) {
1261 setShadowLayer(r, dx, dy, shadowcolor);
1262 }
1263
1264 if (maxlength >= 0) {
1265 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1266 } else {
1267 setFilters(NO_FILTERS);
1268 }
1269
1270 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001271 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001272
1273 /*
1274 * Views are not normally focusable unless specified to be.
1275 * However, TextViews that have input or movement methods *are*
1276 * focusable by default.
1277 */
1278 a = context.obtainStyledAttributes(attrs,
1279 com.android.internal.R.styleable.View,
1280 defStyle, 0);
1281
Gilles Debunne60e21862012-01-30 15:04:14 -08001282 boolean focusable = mMovement != null || getKeyListener() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001283 boolean clickable = focusable;
1284 boolean longClickable = focusable;
1285
1286 n = a.getIndexCount();
1287 for (int i = 0; i < n; i++) {
1288 int attr = a.getIndex(i);
1289
1290 switch (attr) {
1291 case com.android.internal.R.styleable.View_focusable:
1292 focusable = a.getBoolean(attr, focusable);
1293 break;
1294
1295 case com.android.internal.R.styleable.View_clickable:
1296 clickable = a.getBoolean(attr, clickable);
1297 break;
1298
1299 case com.android.internal.R.styleable.View_longClickable:
1300 longClickable = a.getBoolean(attr, longClickable);
1301 break;
1302 }
1303 }
1304 a.recycle();
1305
1306 setFocusable(focusable);
1307 setClickable(clickable);
1308 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001309
Gilles Debunned88876a2012-03-16 17:34:04 -07001310 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001311
1312 // If not explicitly specified this view is important for accessibility.
1313 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1314 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1315 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 }
1317
Raph Leviend570e892012-05-09 11:45:34 -07001318 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001320 if (familyName != null) {
1321 tf = Typeface.create(familyName, styleIndex);
1322 if (tf != null) {
1323 setTypeface(tf);
1324 return;
1325 }
1326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 switch (typefaceIndex) {
1328 case SANS:
1329 tf = Typeface.SANS_SERIF;
1330 break;
1331
1332 case SERIF:
1333 tf = Typeface.SERIF;
1334 break;
1335
1336 case MONOSPACE:
1337 tf = Typeface.MONOSPACE;
1338 break;
1339 }
1340
1341 setTypeface(tf, styleIndex);
1342 }
1343
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001344 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1345 boolean hasRelativeDrawables = (start != null) || (end != null);
1346 if (hasRelativeDrawables) {
1347 Drawables dr = mDrawables;
1348 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001349 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001350 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001351 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001352 final Rect compoundRect = dr.mCompoundRect;
1353 int[] state = getDrawableState();
1354 if (start != null) {
1355 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1356 start.setState(state);
1357 start.copyBounds(compoundRect);
1358 start.setCallback(this);
1359
1360 dr.mDrawableStart = start;
1361 dr.mDrawableSizeStart = compoundRect.width();
1362 dr.mDrawableHeightStart = compoundRect.height();
1363 } else {
1364 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1365 }
1366 if (end != null) {
1367 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1368 end.setState(state);
1369 end.copyBounds(compoundRect);
1370 end.setCallback(this);
1371
1372 dr.mDrawableEnd = end;
1373 dr.mDrawableSizeEnd = compoundRect.width();
1374 dr.mDrawableHeightEnd = compoundRect.height();
1375 } else {
1376 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1377 }
Fabrice Di Meglio4155e2e2013-08-08 16:28:07 -07001378 resetResolvedDrawables();
1379 resolveDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001380 }
1381 }
1382
Janos Levai042856c2010-10-15 02:53:58 +03001383 @Override
1384 public void setEnabled(boolean enabled) {
1385 if (enabled == isEnabled()) {
1386 return;
1387 }
1388
1389 if (!enabled) {
1390 // Hide the soft input if the currently active TextView is disabled
1391 InputMethodManager imm = InputMethodManager.peekInstance();
1392 if (imm != null && imm.isActive(this)) {
1393 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1394 }
1395 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001396
Janos Levai042856c2010-10-15 02:53:58 +03001397 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001398
Dianne Hackbornbc823852011-09-18 17:19:50 -07001399 if (enabled) {
1400 // Make sure IME is updated with current editor info.
1401 InputMethodManager imm = InputMethodManager.peekInstance();
1402 if (imm != null) imm.restartInput(this);
1403 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001404
Gilles Debunne33b7de852012-03-12 11:57:48 -07001405 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001406 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001407 mEditor.invalidateTextDisplayList();
1408 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001409
Gilles Debunned88876a2012-03-16 17:34:04 -07001410 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001411 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001412 }
Janos Levai042856c2010-10-15 02:53:58 +03001413 }
1414
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 /**
1416 * Sets the typeface and style in which the text should be displayed,
1417 * and turns on the fake bold and italic bits in the Paint if the
1418 * Typeface that you provided does not have all the bits in the
1419 * style that you specified.
1420 *
1421 * @attr ref android.R.styleable#TextView_typeface
1422 * @attr ref android.R.styleable#TextView_textStyle
1423 */
1424 public void setTypeface(Typeface tf, int style) {
1425 if (style > 0) {
1426 if (tf == null) {
1427 tf = Typeface.defaultFromStyle(style);
1428 } else {
1429 tf = Typeface.create(tf, style);
1430 }
1431
1432 setTypeface(tf);
1433 // now compute what (if any) algorithmic styling is needed
1434 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1435 int need = style & ~typefaceStyle;
1436 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1437 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1438 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001439 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001440 mTextPaint.setTextSkewX(0);
1441 setTypeface(tf);
1442 }
1443 }
1444
1445 /**
1446 * Subclasses override this to specify that they have a KeyListener
1447 * by default even if not specifically called for in the XML options.
1448 */
1449 protected boolean getDefaultEditable() {
1450 return false;
1451 }
1452
1453 /**
1454 * Subclasses override this to specify a default movement method.
1455 */
1456 protected MovementMethod getDefaultMovementMethod() {
1457 return null;
1458 }
1459
1460 /**
1461 * Return the text the TextView is displaying. If setText() was called with
1462 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1463 * the return value from this method to Spannable or Editable, respectively.
1464 *
1465 * Note: The content of the return value should not be modified. If you want
1466 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001467 *
1468 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 */
1470 @ViewDebug.CapturedViewProperty
1471 public CharSequence getText() {
1472 return mText;
1473 }
1474
1475 /**
1476 * Returns the length, in characters, of the text managed by this TextView
1477 */
1478 public int length() {
1479 return mText.length();
1480 }
1481
1482 /**
1483 * Return the text the TextView is displaying as an Editable object. If
1484 * the text is not editable, null is returned.
1485 *
1486 * @see #getText
1487 */
1488 public Editable getEditableText() {
1489 return (mText instanceof Editable) ? (Editable)mText : null;
1490 }
1491
1492 /**
1493 * @return the height of one standard line in pixels. Note that markup
1494 * within the text can cause individual lines to be taller or shorter
1495 * than this height, and the layout may contain additional first-
1496 * or last-line padding.
1497 */
1498 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001499 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 }
1501
1502 /**
1503 * @return the Layout that is currently being used to display the text.
1504 * This can be null if the text or width has recently changes.
1505 */
1506 public final Layout getLayout() {
1507 return mLayout;
1508 }
1509
1510 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001511 * @return the Layout that is currently being used to display the hint text.
1512 * This can be null.
1513 */
1514 final Layout getHintLayout() {
1515 return mHintLayout;
1516 }
1517
1518 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001519 * Retrieve the {@link android.content.UndoManager} that is currently associated
1520 * with this TextView. By default there is no associated UndoManager, so null
1521 * is returned. One can be associated with the TextView through
1522 * {@link #setUndoManager(android.content.UndoManager, String)}
Dianne Hackbornb811e642013-09-04 17:43:56 -07001523 *
1524 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001525 */
1526 public final UndoManager getUndoManager() {
1527 return mEditor == null ? null : mEditor.mUndoManager;
1528 }
1529
1530 /**
1531 * Associate an {@link android.content.UndoManager} with this TextView. Once
1532 * done, all edit operations on the TextView will result in appropriate
1533 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1534 * stack.
1535 *
1536 * @param undoManager The {@link android.content.UndoManager} to associate with
1537 * this TextView, or null to clear any existing association.
1538 * @param tag String tag identifying this particular TextView owner in the
1539 * UndoManager. This is used to keep the correct association with the
1540 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
Dianne Hackbornb811e642013-09-04 17:43:56 -07001541 *
1542 * @hide
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001543 */
1544 public final void setUndoManager(UndoManager undoManager, String tag) {
1545 if (undoManager != null) {
1546 createEditorIfNeeded();
1547 mEditor.mUndoManager = undoManager;
1548 mEditor.mUndoOwner = undoManager.getOwner(tag, this);
1549 mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor);
1550 if (!(mText instanceof Editable)) {
1551 setText(mText, BufferType.EDITABLE);
1552 }
1553
1554 setFilters((Editable) mText, mFilters);
1555 } else if (mEditor != null) {
1556 // XXX need to destroy all associated state.
1557 mEditor.mUndoManager = null;
1558 mEditor.mUndoOwner = null;
1559 mEditor.mUndoInputFilter = null;
1560 }
1561 }
1562
1563 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001564 * @return the current key listener for this TextView.
1565 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001566 *
1567 * @attr ref android.R.styleable#TextView_numeric
1568 * @attr ref android.R.styleable#TextView_digits
1569 * @attr ref android.R.styleable#TextView_phoneNumber
1570 * @attr ref android.R.styleable#TextView_inputMethod
1571 * @attr ref android.R.styleable#TextView_capitalize
1572 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001573 */
1574 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001575 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001576 }
1577
1578 /**
1579 * Sets the key listener to be used with this TextView. This can be null
1580 * to disallow user input. Note that this method has significant and
1581 * subtle interactions with soft keyboards and other input method:
1582 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1583 * for important details. Calling this method will replace the current
1584 * content type of the text view with the content type returned by the
1585 * key listener.
1586 * <p>
1587 * Be warned that if you want a TextView with a key listener or movement
1588 * method not to be focusable, or if you want a TextView without a
1589 * key listener or movement method to be focusable, you must call
1590 * {@link #setFocusable} again after calling this to get the focusability
1591 * back the way you want it.
1592 *
1593 * @attr ref android.R.styleable#TextView_numeric
1594 * @attr ref android.R.styleable#TextView_digits
1595 * @attr ref android.R.styleable#TextView_phoneNumber
1596 * @attr ref android.R.styleable#TextView_inputMethod
1597 * @attr ref android.R.styleable#TextView_capitalize
1598 * @attr ref android.R.styleable#TextView_autoText
1599 */
1600 public void setKeyListener(KeyListener input) {
1601 setKeyListenerOnly(input);
1602 fixFocusableAndClickableSettings();
1603
1604 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001605 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001606 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001607 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001608 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001609 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001610 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001611 // Change inputType, without affecting transformation.
1612 // No need to applySingleLine since mSingleLine is unchanged.
1613 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001614 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001615 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001616 }
1617
1618 InputMethodManager imm = InputMethodManager.peekInstance();
1619 if (imm != null) imm.restartInput(this);
1620 }
1621
1622 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001623 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001624
Gilles Debunne5fae9962012-05-08 14:53:20 -07001625 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001626 if (mEditor.mKeyListener != input) {
1627 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001628 if (input != null && !(mText instanceof Editable)) {
1629 setText(mText);
1630 }
1631
1632 setFilters((Editable) mText, mFilters);
1633 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001634 }
1635
1636 /**
1637 * @return the movement method being used for this TextView.
1638 * This will frequently be null for non-EditText TextViews.
1639 */
1640 public final MovementMethod getMovementMethod() {
1641 return mMovement;
1642 }
1643
1644 /**
1645 * Sets the movement method (arrow key handler) to be used for
1646 * this TextView. This can be null to disallow using the arrow keys
1647 * to move the cursor or scroll the view.
1648 * <p>
1649 * Be warned that if you want a TextView with a key listener or movement
1650 * method not to be focusable, or if you want a TextView without a
1651 * key listener or movement method to be focusable, you must call
1652 * {@link #setFocusable} again after calling this to get the focusability
1653 * back the way you want it.
1654 */
1655 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001656 if (mMovement != movement) {
1657 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658
Gilles Debunne60e21862012-01-30 15:04:14 -08001659 if (movement != null && !(mText instanceof Spannable)) {
1660 setText(mText);
1661 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001662
Gilles Debunne60e21862012-01-30 15:04:14 -08001663 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001664
Gilles Debunne2d373a12012-04-20 15:32:19 -07001665 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1666 // mMovement
1667 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001668 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001669 }
1670
1671 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001672 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001673 setFocusable(true);
1674 setClickable(true);
1675 setLongClickable(true);
1676 } else {
1677 setFocusable(false);
1678 setClickable(false);
1679 setLongClickable(false);
1680 }
1681 }
1682
1683 /**
1684 * @return the current transformation method for this TextView.
1685 * This will frequently be null except for single-line and password
1686 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001687 *
1688 * @attr ref android.R.styleable#TextView_password
1689 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001690 */
1691 public final TransformationMethod getTransformationMethod() {
1692 return mTransformation;
1693 }
1694
1695 /**
1696 * Sets the transformation that is applied to the text that this
1697 * TextView is displaying.
1698 *
1699 * @attr ref android.R.styleable#TextView_password
1700 * @attr ref android.R.styleable#TextView_singleLine
1701 */
1702 public final void setTransformationMethod(TransformationMethod method) {
1703 if (method == mTransformation) {
1704 // Avoid the setText() below if the transformation is
1705 // the same.
1706 return;
1707 }
1708 if (mTransformation != null) {
1709 if (mText instanceof Spannable) {
1710 ((Spannable) mText).removeSpan(mTransformation);
1711 }
1712 }
1713
1714 mTransformation = method;
1715
Adam Powell7f8f79a2011-07-07 18:35:54 -07001716 if (method instanceof TransformationMethod2) {
1717 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001718 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001719 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1720 } else {
1721 mAllowTransformationLengthChange = false;
1722 }
1723
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001724 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001725
1726 if (hasPasswordTransformationMethod()) {
Alan Viverette77e9a282013-09-12 17:16:09 -07001727 notifyViewAccessibilityStateChangedIfNeeded(
1728 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001729 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001730 }
1731
1732 /**
1733 * Returns the top padding of the view, plus space for the top
1734 * Drawable if any.
1735 */
1736 public int getCompoundPaddingTop() {
1737 final Drawables dr = mDrawables;
1738 if (dr == null || dr.mDrawableTop == null) {
1739 return mPaddingTop;
1740 } else {
1741 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1742 }
1743 }
1744
1745 /**
1746 * Returns the bottom padding of the view, plus space for the bottom
1747 * Drawable if any.
1748 */
1749 public int getCompoundPaddingBottom() {
1750 final Drawables dr = mDrawables;
1751 if (dr == null || dr.mDrawableBottom == null) {
1752 return mPaddingBottom;
1753 } else {
1754 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1755 }
1756 }
1757
1758 /**
1759 * Returns the left padding of the view, plus space for the left
1760 * Drawable if any.
1761 */
1762 public int getCompoundPaddingLeft() {
1763 final Drawables dr = mDrawables;
1764 if (dr == null || dr.mDrawableLeft == null) {
1765 return mPaddingLeft;
1766 } else {
1767 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1768 }
1769 }
1770
1771 /**
1772 * Returns the right padding of the view, plus space for the right
1773 * Drawable if any.
1774 */
1775 public int getCompoundPaddingRight() {
1776 final Drawables dr = mDrawables;
1777 if (dr == null || dr.mDrawableRight == null) {
1778 return mPaddingRight;
1779 } else {
1780 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1781 }
1782 }
1783
1784 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001785 * Returns the start padding of the view, plus space for the start
1786 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001787 */
1788 public int getCompoundPaddingStart() {
1789 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001790 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001791 default:
1792 case LAYOUT_DIRECTION_LTR:
1793 return getCompoundPaddingLeft();
1794 case LAYOUT_DIRECTION_RTL:
1795 return getCompoundPaddingRight();
1796 }
1797 }
1798
1799 /**
1800 * Returns the end padding of the view, plus space for the end
1801 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001802 */
1803 public int getCompoundPaddingEnd() {
1804 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001805 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001806 default:
1807 case LAYOUT_DIRECTION_LTR:
1808 return getCompoundPaddingRight();
1809 case LAYOUT_DIRECTION_RTL:
1810 return getCompoundPaddingLeft();
1811 }
1812 }
1813
1814 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001815 * Returns the extended top padding of the view, including both the
1816 * top Drawable if any and any extra space to keep more than maxLines
1817 * of text from showing. It is only valid to call this after measuring.
1818 */
1819 public int getExtendedPaddingTop() {
1820 if (mMaxMode != LINES) {
1821 return getCompoundPaddingTop();
1822 }
1823
1824 if (mLayout.getLineCount() <= mMaximum) {
1825 return getCompoundPaddingTop();
1826 }
1827
1828 int top = getCompoundPaddingTop();
1829 int bottom = getCompoundPaddingBottom();
1830 int viewht = getHeight() - top - bottom;
1831 int layoutht = mLayout.getLineTop(mMaximum);
1832
1833 if (layoutht >= viewht) {
1834 return top;
1835 }
1836
1837 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1838 if (gravity == Gravity.TOP) {
1839 return top;
1840 } else if (gravity == Gravity.BOTTOM) {
1841 return top + viewht - layoutht;
1842 } else { // (gravity == Gravity.CENTER_VERTICAL)
1843 return top + (viewht - layoutht) / 2;
1844 }
1845 }
1846
1847 /**
1848 * Returns the extended bottom padding of the view, including both the
1849 * bottom Drawable if any and any extra space to keep more than maxLines
1850 * of text from showing. It is only valid to call this after measuring.
1851 */
1852 public int getExtendedPaddingBottom() {
1853 if (mMaxMode != LINES) {
1854 return getCompoundPaddingBottom();
1855 }
1856
1857 if (mLayout.getLineCount() <= mMaximum) {
1858 return getCompoundPaddingBottom();
1859 }
1860
1861 int top = getCompoundPaddingTop();
1862 int bottom = getCompoundPaddingBottom();
1863 int viewht = getHeight() - top - bottom;
1864 int layoutht = mLayout.getLineTop(mMaximum);
1865
1866 if (layoutht >= viewht) {
1867 return bottom;
1868 }
1869
1870 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1871 if (gravity == Gravity.TOP) {
1872 return bottom + viewht - layoutht;
1873 } else if (gravity == Gravity.BOTTOM) {
1874 return bottom;
1875 } else { // (gravity == Gravity.CENTER_VERTICAL)
1876 return bottom + (viewht - layoutht) / 2;
1877 }
1878 }
1879
1880 /**
1881 * Returns the total left padding of the view, including the left
1882 * Drawable if any.
1883 */
1884 public int getTotalPaddingLeft() {
1885 return getCompoundPaddingLeft();
1886 }
1887
1888 /**
1889 * Returns the total right padding of the view, including the right
1890 * Drawable if any.
1891 */
1892 public int getTotalPaddingRight() {
1893 return getCompoundPaddingRight();
1894 }
1895
1896 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001897 * Returns the total start padding of the view, including the start
1898 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001899 */
1900 public int getTotalPaddingStart() {
1901 return getCompoundPaddingStart();
1902 }
1903
1904 /**
1905 * Returns the total end padding of the view, including the end
1906 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001907 */
1908 public int getTotalPaddingEnd() {
1909 return getCompoundPaddingEnd();
1910 }
1911
1912 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001913 * Returns the total top padding of the view, including the top
1914 * Drawable if any, the extra space to keep more than maxLines
1915 * from showing, and the vertical offset for gravity, if any.
1916 */
1917 public int getTotalPaddingTop() {
1918 return getExtendedPaddingTop() + getVerticalOffset(true);
1919 }
1920
1921 /**
1922 * Returns the total bottom padding of the view, including the bottom
1923 * Drawable if any, the extra space to keep more than maxLines
1924 * from showing, and the vertical offset for gravity, if any.
1925 */
1926 public int getTotalPaddingBottom() {
1927 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1928 }
1929
1930 /**
1931 * Sets the Drawables (if any) to appear to the left of, above,
1932 * to the right of, and below the text. Use null if you do not
1933 * want a Drawable there. The Drawables must already have had
1934 * {@link Drawable#setBounds} called.
1935 *
1936 * @attr ref android.R.styleable#TextView_drawableLeft
1937 * @attr ref android.R.styleable#TextView_drawableTop
1938 * @attr ref android.R.styleable#TextView_drawableRight
1939 * @attr ref android.R.styleable#TextView_drawableBottom
1940 */
1941 public void setCompoundDrawables(Drawable left, Drawable top,
1942 Drawable right, Drawable bottom) {
1943 Drawables dr = mDrawables;
1944
1945 final boolean drawables = left != null || top != null
1946 || right != null || bottom != null;
1947
1948 if (!drawables) {
1949 // Clearing drawables... can we free the data structure?
1950 if (dr != null) {
1951 if (dr.mDrawablePadding == 0) {
1952 mDrawables = null;
1953 } else {
1954 // We need to retain the last set padding, so just clear
1955 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001956 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001957 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001958 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001959 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001960 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001961 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001962 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001963 dr.mDrawableBottom = null;
1964 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1965 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1966 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1967 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1968 }
1969 }
1970 } else {
1971 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001972 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001973 }
1974
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001975 mDrawables.mOverride = false;
1976
Romain Guy48540eb2009-05-19 16:44:57 -07001977 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1978 dr.mDrawableLeft.setCallback(null);
1979 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001980 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001981
1982 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001983 dr.mDrawableTop.setCallback(null);
1984 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001985 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001986
1987 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001988 dr.mDrawableRight.setCallback(null);
1989 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001990 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001991
1992 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001993 dr.mDrawableBottom.setCallback(null);
1994 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001995 dr.mDrawableBottom = bottom;
1996
1997 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001998 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001999
2000 state = getDrawableState();
2001
2002 if (left != null) {
2003 left.setState(state);
2004 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002005 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002006 dr.mDrawableSizeLeft = compoundRect.width();
2007 dr.mDrawableHeightLeft = compoundRect.height();
2008 } else {
2009 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2010 }
2011
2012 if (right != null) {
2013 right.setState(state);
2014 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002015 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002016 dr.mDrawableSizeRight = compoundRect.width();
2017 dr.mDrawableHeightRight = compoundRect.height();
2018 } else {
2019 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2020 }
2021
2022 if (top != null) {
2023 top.setState(state);
2024 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002025 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002026 dr.mDrawableSizeTop = compoundRect.height();
2027 dr.mDrawableWidthTop = compoundRect.width();
2028 } else {
2029 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2030 }
2031
2032 if (bottom != null) {
2033 bottom.setState(state);
2034 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002035 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002036 dr.mDrawableSizeBottom = compoundRect.height();
2037 dr.mDrawableWidthBottom = compoundRect.width();
2038 } else {
2039 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2040 }
2041 }
2042
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002043 // Save initial left/right drawables
2044 if (dr != null) {
2045 dr.mDrawableLeftInitial = left;
2046 dr.mDrawableRightInitial = right;
2047 }
2048
Fabrice Di Meglio3f5a90b2013-06-24 19:22:25 -07002049 resetResolvedDrawables();
2050 resolveDrawables();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002051 invalidate();
2052 requestLayout();
2053 }
2054
2055 /**
2056 * Sets the Drawables (if any) to appear to the left of, above,
2057 * to the right of, and below the text. Use 0 if you do not
2058 * want a Drawable there. The Drawables' bounds will be set to
2059 * their intrinsic bounds.
2060 *
2061 * @param left Resource identifier of the left Drawable.
2062 * @param top Resource identifier of the top Drawable.
2063 * @param right Resource identifier of the right Drawable.
2064 * @param bottom Resource identifier of the bottom Drawable.
2065 *
2066 * @attr ref android.R.styleable#TextView_drawableLeft
2067 * @attr ref android.R.styleable#TextView_drawableTop
2068 * @attr ref android.R.styleable#TextView_drawableRight
2069 * @attr ref android.R.styleable#TextView_drawableBottom
2070 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002071 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002072 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
2073 final Resources resources = getContext().getResources();
2074 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
2075 top != 0 ? resources.getDrawable(top) : null,
2076 right != 0 ? resources.getDrawable(right) : null,
2077 bottom != 0 ? resources.getDrawable(bottom) : null);
2078 }
2079
2080 /**
2081 * Sets the Drawables (if any) to appear to the left of, above,
2082 * to the right of, and below the text. Use null if you do not
2083 * want a Drawable there. The Drawables' bounds will be set to
2084 * their intrinsic bounds.
2085 *
2086 * @attr ref android.R.styleable#TextView_drawableLeft
2087 * @attr ref android.R.styleable#TextView_drawableTop
2088 * @attr ref android.R.styleable#TextView_drawableRight
2089 * @attr ref android.R.styleable#TextView_drawableBottom
2090 */
2091 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
2092 Drawable right, Drawable bottom) {
2093
2094 if (left != null) {
2095 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2096 }
2097 if (right != null) {
2098 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2099 }
2100 if (top != null) {
2101 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2102 }
2103 if (bottom != null) {
2104 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2105 }
2106 setCompoundDrawables(left, top, right, bottom);
2107 }
2108
2109 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002110 * Sets the Drawables (if any) to appear to the start of, above,
2111 * to the end of, and below the text. Use null if you do not
2112 * want a Drawable there. The Drawables must already have had
2113 * {@link Drawable#setBounds} called.
2114 *
2115 * @attr ref android.R.styleable#TextView_drawableStart
2116 * @attr ref android.R.styleable#TextView_drawableTop
2117 * @attr ref android.R.styleable#TextView_drawableEnd
2118 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002119 */
2120 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2121 Drawable end, Drawable bottom) {
2122 Drawables dr = mDrawables;
2123
2124 final boolean drawables = start != null || top != null
2125 || end != null || bottom != null;
2126
2127 if (!drawables) {
2128 // Clearing drawables... can we free the data structure?
2129 if (dr != null) {
2130 if (dr.mDrawablePadding == 0) {
2131 mDrawables = null;
2132 } else {
2133 // We need to retain the last set padding, so just clear
2134 // out all of the fields in the existing structure.
2135 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2136 dr.mDrawableStart = null;
2137 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2138 dr.mDrawableTop = null;
2139 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2140 dr.mDrawableEnd = null;
2141 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2142 dr.mDrawableBottom = null;
2143 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2144 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2145 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2146 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2147 }
2148 }
2149 } else {
2150 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002151 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002152 }
2153
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002154 mDrawables.mOverride = true;
2155
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002156 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2157 dr.mDrawableStart.setCallback(null);
2158 }
2159 dr.mDrawableStart = start;
2160
2161 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2162 dr.mDrawableTop.setCallback(null);
2163 }
2164 dr.mDrawableTop = top;
2165
2166 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2167 dr.mDrawableEnd.setCallback(null);
2168 }
2169 dr.mDrawableEnd = end;
2170
2171 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2172 dr.mDrawableBottom.setCallback(null);
2173 }
2174 dr.mDrawableBottom = bottom;
2175
2176 final Rect compoundRect = dr.mCompoundRect;
2177 int[] state;
2178
2179 state = getDrawableState();
2180
2181 if (start != null) {
2182 start.setState(state);
2183 start.copyBounds(compoundRect);
2184 start.setCallback(this);
2185 dr.mDrawableSizeStart = compoundRect.width();
2186 dr.mDrawableHeightStart = compoundRect.height();
2187 } else {
2188 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2189 }
2190
2191 if (end != null) {
2192 end.setState(state);
2193 end.copyBounds(compoundRect);
2194 end.setCallback(this);
2195 dr.mDrawableSizeEnd = compoundRect.width();
2196 dr.mDrawableHeightEnd = compoundRect.height();
2197 } else {
2198 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2199 }
2200
2201 if (top != null) {
2202 top.setState(state);
2203 top.copyBounds(compoundRect);
2204 top.setCallback(this);
2205 dr.mDrawableSizeTop = compoundRect.height();
2206 dr.mDrawableWidthTop = compoundRect.width();
2207 } else {
2208 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2209 }
2210
2211 if (bottom != null) {
2212 bottom.setState(state);
2213 bottom.copyBounds(compoundRect);
2214 bottom.setCallback(this);
2215 dr.mDrawableSizeBottom = compoundRect.height();
2216 dr.mDrawableWidthBottom = compoundRect.width();
2217 } else {
2218 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2219 }
2220 }
2221
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002222 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002223 resolveDrawables();
2224 invalidate();
2225 requestLayout();
2226 }
2227
2228 /**
2229 * Sets the Drawables (if any) to appear to the start of, above,
2230 * to the end of, and below the text. Use 0 if you do not
2231 * want a Drawable there. The Drawables' bounds will be set to
2232 * their intrinsic bounds.
2233 *
2234 * @param start Resource identifier of the start Drawable.
2235 * @param top Resource identifier of the top Drawable.
2236 * @param end Resource identifier of the end Drawable.
2237 * @param bottom Resource identifier of the bottom Drawable.
2238 *
2239 * @attr ref android.R.styleable#TextView_drawableStart
2240 * @attr ref android.R.styleable#TextView_drawableTop
2241 * @attr ref android.R.styleable#TextView_drawableEnd
2242 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002243 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002244 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002245 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2246 int bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002247 final Resources resources = getContext().getResources();
2248 setCompoundDrawablesRelativeWithIntrinsicBounds(
2249 start != 0 ? resources.getDrawable(start) : null,
2250 top != 0 ? resources.getDrawable(top) : null,
2251 end != 0 ? resources.getDrawable(end) : null,
2252 bottom != 0 ? resources.getDrawable(bottom) : null);
2253 }
2254
2255 /**
2256 * Sets the Drawables (if any) to appear to the start of, above,
2257 * to the end of, and below the text. Use null if you do not
2258 * want a Drawable there. The Drawables' bounds will be set to
2259 * their intrinsic bounds.
2260 *
2261 * @attr ref android.R.styleable#TextView_drawableStart
2262 * @attr ref android.R.styleable#TextView_drawableTop
2263 * @attr ref android.R.styleable#TextView_drawableEnd
2264 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002265 */
2266 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2267 Drawable end, Drawable bottom) {
2268
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002269 if (start != null) {
2270 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2271 }
2272 if (end != null) {
2273 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2274 }
2275 if (top != null) {
2276 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2277 }
2278 if (bottom != null) {
2279 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2280 }
2281 setCompoundDrawablesRelative(start, top, end, bottom);
2282 }
2283
2284 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002285 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002286 *
2287 * @attr ref android.R.styleable#TextView_drawableLeft
2288 * @attr ref android.R.styleable#TextView_drawableTop
2289 * @attr ref android.R.styleable#TextView_drawableRight
2290 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002291 */
2292 public Drawable[] getCompoundDrawables() {
2293 final Drawables dr = mDrawables;
2294 if (dr != null) {
2295 return new Drawable[] {
2296 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2297 };
2298 } else {
2299 return new Drawable[] { null, null, null, null };
2300 }
2301 }
2302
2303 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002304 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002305 *
2306 * @attr ref android.R.styleable#TextView_drawableStart
2307 * @attr ref android.R.styleable#TextView_drawableTop
2308 * @attr ref android.R.styleable#TextView_drawableEnd
2309 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002310 */
2311 public Drawable[] getCompoundDrawablesRelative() {
2312 final Drawables dr = mDrawables;
2313 if (dr != null) {
2314 return new Drawable[] {
2315 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2316 };
2317 } else {
2318 return new Drawable[] { null, null, null, null };
2319 }
2320 }
2321
2322 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 * Sets the size of the padding between the compound drawables and
2324 * the text.
2325 *
2326 * @attr ref android.R.styleable#TextView_drawablePadding
2327 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002328 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002329 public void setCompoundDrawablePadding(int pad) {
2330 Drawables dr = mDrawables;
2331 if (pad == 0) {
2332 if (dr != null) {
2333 dr.mDrawablePadding = pad;
2334 }
2335 } else {
2336 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002337 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002338 }
2339 dr.mDrawablePadding = pad;
2340 }
2341
2342 invalidate();
2343 requestLayout();
2344 }
2345
2346 /**
2347 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002348 *
2349 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002350 */
2351 public int getCompoundDrawablePadding() {
2352 final Drawables dr = mDrawables;
2353 return dr != null ? dr.mDrawablePadding : 0;
2354 }
2355
2356 @Override
2357 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002358 if (left != mPaddingLeft ||
2359 right != mPaddingRight ||
2360 top != mPaddingTop ||
2361 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002362 nullLayouts();
2363 }
2364
2365 // the super call will requestLayout()
2366 super.setPadding(left, top, right, bottom);
2367 invalidate();
2368 }
2369
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002370 @Override
2371 public void setPaddingRelative(int start, int top, int end, int bottom) {
2372 if (start != getPaddingStart() ||
2373 end != getPaddingEnd() ||
2374 top != mPaddingTop ||
2375 bottom != mPaddingBottom) {
2376 nullLayouts();
2377 }
2378
2379 // the super call will requestLayout()
2380 super.setPaddingRelative(start, top, end, bottom);
2381 invalidate();
2382 }
2383
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002384 /**
2385 * Gets the autolink mask of the text. See {@link
2386 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2387 * possible values.
2388 *
2389 * @attr ref android.R.styleable#TextView_autoLink
2390 */
2391 public final int getAutoLinkMask() {
2392 return mAutoLinkMask;
2393 }
2394
2395 /**
2396 * Sets the text color, size, style, hint color, and highlight color
2397 * from the specified TextAppearance resource.
2398 */
2399 public void setTextAppearance(Context context, int resid) {
2400 TypedArray appearance =
2401 context.obtainStyledAttributes(resid,
2402 com.android.internal.R.styleable.TextAppearance);
2403
2404 int color;
2405 ColorStateList colors;
2406 int ts;
2407
Gilles Debunne2d373a12012-04-20 15:32:19 -07002408 color = appearance.getColor(
2409 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002410 if (color != 0) {
2411 setHighlightColor(color);
2412 }
2413
2414 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2415 TextAppearance_textColor);
2416 if (colors != null) {
2417 setTextColor(colors);
2418 }
2419
2420 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2421 TextAppearance_textSize, 0);
2422 if (ts != 0) {
2423 setRawTextSize(ts);
2424 }
2425
2426 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2427 TextAppearance_textColorHint);
2428 if (colors != null) {
2429 setHintTextColor(colors);
2430 }
2431
2432 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2433 TextAppearance_textColorLink);
2434 if (colors != null) {
2435 setLinkTextColor(colors);
2436 }
2437
Raph Leviend570e892012-05-09 11:45:34 -07002438 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002439 int typefaceIndex, styleIndex;
2440
Raph Leviend570e892012-05-09 11:45:34 -07002441 familyName = appearance.getString(com.android.internal.R.styleable.
2442 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002443 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2444 TextAppearance_typeface, -1);
2445 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2446 TextAppearance_textStyle, -1);
2447
Raph Leviend570e892012-05-09 11:45:34 -07002448 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002449
Adam Powellac91df82013-02-14 13:48:47 -08002450 final int shadowcolor = appearance.getInt(
2451 com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
2452 if (shadowcolor != 0) {
2453 final float dx = appearance.getFloat(
2454 com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
2455 final float dy = appearance.getFloat(
2456 com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
2457 final float r = appearance.getFloat(
2458 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
2459
2460 setShadowLayer(r, dx, dy, shadowcolor);
2461 }
2462
Adam Powell7f8f79a2011-07-07 18:35:54 -07002463 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2464 false)) {
2465 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2466 }
2467
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002468 appearance.recycle();
2469 }
2470
2471 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002472 * Get the default {@link Locale} of the text in this TextView.
2473 * @return the default {@link Locale} of the text in this TextView.
2474 */
2475 public Locale getTextLocale() {
2476 return mTextPaint.getTextLocale();
2477 }
2478
2479 /**
2480 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2481 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2482 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2483 *
2484 * @param locale the {@link Locale} for drawing text, must not be null.
2485 *
2486 * @see Paint#setTextLocale
2487 */
2488 public void setTextLocale(Locale locale) {
2489 mTextPaint.setTextLocale(locale);
2490 }
2491
2492 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002493 * @return the size (in pixels) of the default text size in this TextView.
2494 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002495 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002496 public float getTextSize() {
2497 return mTextPaint.getTextSize();
2498 }
2499
2500 /**
2501 * Set the default text size to the given value, interpreted as "scaled
2502 * pixel" units. This size is adjusted based on the current density and
2503 * user font size preference.
2504 *
2505 * @param size The scaled pixel size.
2506 *
2507 * @attr ref android.R.styleable#TextView_textSize
2508 */
2509 @android.view.RemotableViewMethod
2510 public void setTextSize(float size) {
2511 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2512 }
2513
2514 /**
2515 * Set the default text size to a given unit and value. See {@link
2516 * TypedValue} for the possible dimension units.
2517 *
2518 * @param unit The desired dimension unit.
2519 * @param size The desired size in the given units.
2520 *
2521 * @attr ref android.R.styleable#TextView_textSize
2522 */
2523 public void setTextSize(int unit, float size) {
2524 Context c = getContext();
2525 Resources r;
2526
2527 if (c == null)
2528 r = Resources.getSystem();
2529 else
2530 r = c.getResources();
2531
2532 setRawTextSize(TypedValue.applyDimension(
2533 unit, size, r.getDisplayMetrics()));
2534 }
2535
2536 private void setRawTextSize(float size) {
2537 if (size != mTextPaint.getTextSize()) {
2538 mTextPaint.setTextSize(size);
2539
2540 if (mLayout != null) {
2541 nullLayouts();
2542 requestLayout();
2543 invalidate();
2544 }
2545 }
2546 }
2547
2548 /**
2549 * @return the extent by which text is currently being stretched
2550 * horizontally. This will usually be 1.
2551 */
2552 public float getTextScaleX() {
2553 return mTextPaint.getTextScaleX();
2554 }
2555
2556 /**
2557 * Sets the extent by which text should be stretched horizontally.
2558 *
2559 * @attr ref android.R.styleable#TextView_textScaleX
2560 */
2561 @android.view.RemotableViewMethod
2562 public void setTextScaleX(float size) {
2563 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002564 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002565 mTextPaint.setTextScaleX(size);
2566
2567 if (mLayout != null) {
2568 nullLayouts();
2569 requestLayout();
2570 invalidate();
2571 }
2572 }
2573 }
2574
2575 /**
2576 * Sets the typeface and style in which the text should be displayed.
2577 * Note that not all Typeface families actually have bold and italic
2578 * variants, so you may need to use
2579 * {@link #setTypeface(Typeface, int)} to get the appearance
2580 * that you actually want.
2581 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002582 * @see #getTypeface()
2583 *
Raph Leviend570e892012-05-09 11:45:34 -07002584 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002585 * @attr ref android.R.styleable#TextView_typeface
2586 * @attr ref android.R.styleable#TextView_textStyle
2587 */
2588 public void setTypeface(Typeface tf) {
2589 if (mTextPaint.getTypeface() != tf) {
2590 mTextPaint.setTypeface(tf);
2591
2592 if (mLayout != null) {
2593 nullLayouts();
2594 requestLayout();
2595 invalidate();
2596 }
2597 }
2598 }
2599
2600 /**
2601 * @return the current typeface and style in which the text is being
2602 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002603 *
2604 * @see #setTypeface(Typeface)
2605 *
Raph Leviend570e892012-05-09 11:45:34 -07002606 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002607 * @attr ref android.R.styleable#TextView_typeface
2608 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002609 */
2610 public Typeface getTypeface() {
2611 return mTextPaint.getTypeface();
2612 }
2613
2614 /**
2615 * Sets the text color for all the states (normal, selected,
2616 * focused) to be this color.
2617 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002618 * @see #setTextColor(ColorStateList)
2619 * @see #getTextColors()
2620 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002621 * @attr ref android.R.styleable#TextView_textColor
2622 */
2623 @android.view.RemotableViewMethod
2624 public void setTextColor(int color) {
2625 mTextColor = ColorStateList.valueOf(color);
2626 updateTextColors();
2627 }
2628
2629 /**
2630 * Sets the text color.
2631 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002632 * @see #setTextColor(int)
2633 * @see #getTextColors()
2634 * @see #setHintTextColor(ColorStateList)
2635 * @see #setLinkTextColor(ColorStateList)
2636 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002637 * @attr ref android.R.styleable#TextView_textColor
2638 */
2639 public void setTextColor(ColorStateList colors) {
2640 if (colors == null) {
2641 throw new NullPointerException();
2642 }
2643
2644 mTextColor = colors;
2645 updateTextColors();
2646 }
2647
2648 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002649 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002650 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002651 * @see #setTextColor(ColorStateList)
2652 * @see #setTextColor(int)
2653 *
2654 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002655 */
2656 public final ColorStateList getTextColors() {
2657 return mTextColor;
2658 }
2659
2660 /**
2661 * <p>Return the current color selected for normal text.</p>
2662 *
2663 * @return Returns the current text color.
2664 */
2665 public final int getCurrentTextColor() {
2666 return mCurTextColor;
2667 }
2668
2669 /**
2670 * Sets the color used to display the selection highlight.
2671 *
2672 * @attr ref android.R.styleable#TextView_textColorHighlight
2673 */
2674 @android.view.RemotableViewMethod
2675 public void setHighlightColor(int color) {
2676 if (mHighlightColor != color) {
2677 mHighlightColor = color;
2678 invalidate();
2679 }
2680 }
2681
2682 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002683 * @return the color used to display the selection highlight
2684 *
2685 * @see #setHighlightColor(int)
2686 *
2687 * @attr ref android.R.styleable#TextView_textColorHighlight
2688 */
2689 public int getHighlightColor() {
2690 return mHighlightColor;
2691 }
2692
2693 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002694 * Sets whether the soft input method will be made visible when this
2695 * TextView gets focused. The default is true.
2696 * @hide
2697 */
2698 @android.view.RemotableViewMethod
2699 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002700 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002701 mEditor.mShowSoftInputOnFocus = show;
2702 }
2703
2704 /**
2705 * Returns whether the soft input method will be made visible when this
2706 * TextView gets focused. The default is true.
2707 * @hide
2708 */
2709 public final boolean getShowSoftInputOnFocus() {
2710 // When there is no Editor, return default true value
2711 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2712 }
2713
2714 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002715 * Gives the text a shadow of the specified radius and color, the specified
2716 * distance from its normal position.
2717 *
2718 * @attr ref android.R.styleable#TextView_shadowColor
2719 * @attr ref android.R.styleable#TextView_shadowDx
2720 * @attr ref android.R.styleable#TextView_shadowDy
2721 * @attr ref android.R.styleable#TextView_shadowRadius
2722 */
2723 public void setShadowLayer(float radius, float dx, float dy, int color) {
2724 mTextPaint.setShadowLayer(radius, dx, dy, color);
2725
2726 mShadowRadius = radius;
2727 mShadowDx = dx;
2728 mShadowDy = dy;
2729
Gilles Debunne33b7de852012-03-12 11:57:48 -07002730 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002731 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002732 invalidate();
2733 }
2734
2735 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002736 * Gets the radius of the shadow layer.
2737 *
2738 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2739 *
2740 * @see #setShadowLayer(float, float, float, int)
2741 *
2742 * @attr ref android.R.styleable#TextView_shadowRadius
2743 */
2744 public float getShadowRadius() {
2745 return mShadowRadius;
2746 }
2747
2748 /**
2749 * @return the horizontal offset of the shadow layer
2750 *
2751 * @see #setShadowLayer(float, float, float, int)
2752 *
2753 * @attr ref android.R.styleable#TextView_shadowDx
2754 */
2755 public float getShadowDx() {
2756 return mShadowDx;
2757 }
2758
2759 /**
2760 * @return the vertical offset of the shadow layer
2761 *
2762 * @see #setShadowLayer(float, float, float, int)
2763 *
2764 * @attr ref android.R.styleable#TextView_shadowDy
2765 */
2766 public float getShadowDy() {
2767 return mShadowDy;
2768 }
2769
2770 /**
2771 * @return the color of the shadow layer
2772 *
2773 * @see #setShadowLayer(float, float, float, int)
2774 *
2775 * @attr ref android.R.styleable#TextView_shadowColor
2776 */
2777 public int getShadowColor() {
2778 return mTextPaint.shadowColor;
2779 }
2780
2781 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002782 * @return the base paint used for the text. Please use this only to
2783 * consult the Paint's properties and not to change them.
2784 */
2785 public TextPaint getPaint() {
2786 return mTextPaint;
2787 }
2788
2789 /**
2790 * Sets the autolink mask of the text. See {@link
2791 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2792 * possible values.
2793 *
2794 * @attr ref android.R.styleable#TextView_autoLink
2795 */
2796 @android.view.RemotableViewMethod
2797 public final void setAutoLinkMask(int mask) {
2798 mAutoLinkMask = mask;
2799 }
2800
2801 /**
2802 * Sets whether the movement method will automatically be set to
2803 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2804 * set to nonzero and links are detected in {@link #setText}.
2805 * The default is true.
2806 *
2807 * @attr ref android.R.styleable#TextView_linksClickable
2808 */
2809 @android.view.RemotableViewMethod
2810 public final void setLinksClickable(boolean whether) {
2811 mLinksClickable = whether;
2812 }
2813
2814 /**
2815 * Returns whether the movement method will automatically be set to
2816 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2817 * set to nonzero and links are detected in {@link #setText}.
2818 * The default is true.
2819 *
2820 * @attr ref android.R.styleable#TextView_linksClickable
2821 */
2822 public final boolean getLinksClickable() {
2823 return mLinksClickable;
2824 }
2825
2826 /**
2827 * Returns the list of URLSpans attached to the text
2828 * (by {@link Linkify} or otherwise) if any. You can call
2829 * {@link URLSpan#getURL} on them to find where they link to
2830 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2831 * to find the region of the text they are attached to.
2832 */
2833 public URLSpan[] getUrls() {
2834 if (mText instanceof Spanned) {
2835 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2836 } else {
2837 return new URLSpan[0];
2838 }
2839 }
2840
2841 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002842 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2843 * TextView.
2844 *
2845 * @see #setHintTextColor(ColorStateList)
2846 * @see #getHintTextColors()
2847 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002848 *
2849 * @attr ref android.R.styleable#TextView_textColorHint
2850 */
2851 @android.view.RemotableViewMethod
2852 public final void setHintTextColor(int color) {
2853 mHintTextColor = ColorStateList.valueOf(color);
2854 updateTextColors();
2855 }
2856
2857 /**
2858 * Sets the color of the hint text.
2859 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002860 * @see #getHintTextColors()
2861 * @see #setHintTextColor(int)
2862 * @see #setTextColor(ColorStateList)
2863 * @see #setLinkTextColor(ColorStateList)
2864 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002865 * @attr ref android.R.styleable#TextView_textColorHint
2866 */
2867 public final void setHintTextColor(ColorStateList colors) {
2868 mHintTextColor = colors;
2869 updateTextColors();
2870 }
2871
2872 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002873 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002874 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002875 * @see #setHintTextColor(ColorStateList)
2876 * @see #setHintTextColor(int)
2877 * @see #setTextColor(ColorStateList)
2878 * @see #setLinkTextColor(ColorStateList)
2879 *
2880 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002881 */
2882 public final ColorStateList getHintTextColors() {
2883 return mHintTextColor;
2884 }
2885
2886 /**
2887 * <p>Return the current color selected to paint the hint text.</p>
2888 *
2889 * @return Returns the current hint text color.
2890 */
2891 public final int getCurrentHintTextColor() {
2892 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2893 }
2894
2895 /**
2896 * Sets the color of links in the text.
2897 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002898 * @see #setLinkTextColor(ColorStateList)
2899 * @see #getLinkTextColors()
2900 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002901 * @attr ref android.R.styleable#TextView_textColorLink
2902 */
2903 @android.view.RemotableViewMethod
2904 public final void setLinkTextColor(int color) {
2905 mLinkTextColor = ColorStateList.valueOf(color);
2906 updateTextColors();
2907 }
2908
2909 /**
2910 * Sets the color of links in the text.
2911 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002912 * @see #setLinkTextColor(int)
2913 * @see #getLinkTextColors()
2914 * @see #setTextColor(ColorStateList)
2915 * @see #setHintTextColor(ColorStateList)
2916 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002917 * @attr ref android.R.styleable#TextView_textColorLink
2918 */
2919 public final void setLinkTextColor(ColorStateList colors) {
2920 mLinkTextColor = colors;
2921 updateTextColors();
2922 }
2923
2924 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002925 * @return the list of colors used to paint the links in the text, for the different states of
2926 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002927 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002928 * @see #setLinkTextColor(ColorStateList)
2929 * @see #setLinkTextColor(int)
2930 *
2931 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002932 */
2933 public final ColorStateList getLinkTextColors() {
2934 return mLinkTextColor;
2935 }
2936
2937 /**
2938 * Sets the horizontal alignment of the text and the
2939 * vertical gravity that will be used when there is extra space
2940 * in the TextView beyond what is required for the text itself.
2941 *
2942 * @see android.view.Gravity
2943 * @attr ref android.R.styleable#TextView_gravity
2944 */
2945 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002946 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002947 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002948 }
2949 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2950 gravity |= Gravity.TOP;
2951 }
2952
2953 boolean newLayout = false;
2954
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002955 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2956 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002957 newLayout = true;
2958 }
2959
2960 if (gravity != mGravity) {
2961 invalidate();
2962 }
2963
2964 mGravity = gravity;
2965
2966 if (mLayout != null && newLayout) {
2967 // XXX this is heavy-handed because no actual content changes.
2968 int want = mLayout.getWidth();
2969 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2970
2971 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2972 mRight - mLeft - getCompoundPaddingLeft() -
2973 getCompoundPaddingRight(), true);
2974 }
2975 }
2976
2977 /**
2978 * Returns the horizontal and vertical alignment of this TextView.
2979 *
2980 * @see android.view.Gravity
2981 * @attr ref android.R.styleable#TextView_gravity
2982 */
2983 public int getGravity() {
2984 return mGravity;
2985 }
2986
2987 /**
2988 * @return the flags on the Paint being used to display the text.
2989 * @see Paint#getFlags
2990 */
2991 public int getPaintFlags() {
2992 return mTextPaint.getFlags();
2993 }
2994
2995 /**
2996 * Sets flags on the Paint being used to display the text and
2997 * reflows the text if they are different from the old flags.
2998 * @see Paint#setFlags
2999 */
3000 @android.view.RemotableViewMethod
3001 public void setPaintFlags(int flags) {
3002 if (mTextPaint.getFlags() != flags) {
3003 mTextPaint.setFlags(flags);
3004
3005 if (mLayout != null) {
3006 nullLayouts();
3007 requestLayout();
3008 invalidate();
3009 }
3010 }
3011 }
3012
3013 /**
3014 * Sets whether the text should be allowed to be wider than the
3015 * View is. If false, it will be wrapped to the width of the View.
3016 *
3017 * @attr ref android.R.styleable#TextView_scrollHorizontally
3018 */
3019 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003020 if (mHorizontallyScrolling != whether) {
3021 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003022
Gilles Debunne22378292011-08-12 10:38:52 -07003023 if (mLayout != null) {
3024 nullLayouts();
3025 requestLayout();
3026 invalidate();
3027 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003028 }
3029 }
3030
3031 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003032 * Returns whether the text is allowed to be wider than the View is.
3033 * If false, the text will be wrapped to the width of the View.
3034 *
3035 * @attr ref android.R.styleable#TextView_scrollHorizontally
3036 * @hide
3037 */
3038 public boolean getHorizontallyScrolling() {
3039 return mHorizontallyScrolling;
3040 }
3041
3042 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003043 * Makes the TextView at least this many lines tall.
3044 *
3045 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3046 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003047 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003048 * @see #getMinLines()
3049 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003050 * @attr ref android.R.styleable#TextView_minLines
3051 */
3052 @android.view.RemotableViewMethod
3053 public void setMinLines(int minlines) {
3054 mMinimum = minlines;
3055 mMinMode = LINES;
3056
3057 requestLayout();
3058 invalidate();
3059 }
3060
3061 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003062 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3063 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3064 *
3065 * @see #setMinLines(int)
3066 *
3067 * @attr ref android.R.styleable#TextView_minLines
3068 */
3069 public int getMinLines() {
3070 return mMinMode == LINES ? mMinimum : -1;
3071 }
3072
3073 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003074 * Makes the TextView at least this many pixels tall.
3075 *
3076 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003077 *
3078 * @attr ref android.R.styleable#TextView_minHeight
3079 */
3080 @android.view.RemotableViewMethod
3081 public void setMinHeight(int minHeight) {
3082 mMinimum = minHeight;
3083 mMinMode = PIXELS;
3084
3085 requestLayout();
3086 invalidate();
3087 }
3088
3089 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003090 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3091 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3092 *
3093 * @see #setMinHeight(int)
3094 *
3095 * @attr ref android.R.styleable#TextView_minHeight
3096 */
3097 public int getMinHeight() {
3098 return mMinMode == PIXELS ? mMinimum : -1;
3099 }
3100
3101 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003102 * Makes the TextView at most this many lines tall.
3103 *
3104 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003105 *
3106 * @attr ref android.R.styleable#TextView_maxLines
3107 */
3108 @android.view.RemotableViewMethod
3109 public void setMaxLines(int maxlines) {
3110 mMaximum = maxlines;
3111 mMaxMode = LINES;
3112
3113 requestLayout();
3114 invalidate();
3115 }
3116
3117 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003118 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3119 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3120 *
3121 * @see #setMaxLines(int)
3122 *
3123 * @attr ref android.R.styleable#TextView_maxLines
3124 */
3125 public int getMaxLines() {
3126 return mMaxMode == LINES ? mMaximum : -1;
3127 }
3128
3129 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003130 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3131 * {@link #setMaxLines(int)} method.
3132 *
3133 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003134 *
3135 * @attr ref android.R.styleable#TextView_maxHeight
3136 */
3137 @android.view.RemotableViewMethod
3138 public void setMaxHeight(int maxHeight) {
3139 mMaximum = maxHeight;
3140 mMaxMode = PIXELS;
3141
3142 requestLayout();
3143 invalidate();
3144 }
3145
3146 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003147 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3148 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3149 *
3150 * @see #setMaxHeight(int)
3151 *
3152 * @attr ref android.R.styleable#TextView_maxHeight
3153 */
3154 public int getMaxHeight() {
3155 return mMaxMode == PIXELS ? mMaximum : -1;
3156 }
3157
3158 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003159 * Makes the TextView exactly this many lines tall.
3160 *
3161 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3162 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003163 *
3164 * @attr ref android.R.styleable#TextView_lines
3165 */
3166 @android.view.RemotableViewMethod
3167 public void setLines(int lines) {
3168 mMaximum = mMinimum = lines;
3169 mMaxMode = mMinMode = LINES;
3170
3171 requestLayout();
3172 invalidate();
3173 }
3174
3175 /**
3176 * Makes the TextView exactly this many pixels tall.
3177 * You could do the same thing by specifying this number in the
3178 * LayoutParams.
3179 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003180 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3181 * height setting.
3182 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003183 * @attr ref android.R.styleable#TextView_height
3184 */
3185 @android.view.RemotableViewMethod
3186 public void setHeight(int pixels) {
3187 mMaximum = mMinimum = pixels;
3188 mMaxMode = mMinMode = PIXELS;
3189
3190 requestLayout();
3191 invalidate();
3192 }
3193
3194 /**
3195 * Makes the TextView at least this many ems wide
3196 *
3197 * @attr ref android.R.styleable#TextView_minEms
3198 */
3199 @android.view.RemotableViewMethod
3200 public void setMinEms(int minems) {
3201 mMinWidth = minems;
3202 mMinWidthMode = EMS;
3203
3204 requestLayout();
3205 invalidate();
3206 }
3207
3208 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003209 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3210 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3211 *
3212 * @see #setMinEms(int)
3213 * @see #setEms(int)
3214 *
3215 * @attr ref android.R.styleable#TextView_minEms
3216 */
3217 public int getMinEms() {
3218 return mMinWidthMode == EMS ? mMinWidth : -1;
3219 }
3220
3221 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003222 * Makes the TextView at least this many pixels wide
3223 *
3224 * @attr ref android.R.styleable#TextView_minWidth
3225 */
3226 @android.view.RemotableViewMethod
3227 public void setMinWidth(int minpixels) {
3228 mMinWidth = minpixels;
3229 mMinWidthMode = PIXELS;
3230
3231 requestLayout();
3232 invalidate();
3233 }
3234
3235 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003236 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3237 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3238 *
3239 * @see #setMinWidth(int)
3240 * @see #setWidth(int)
3241 *
3242 * @attr ref android.R.styleable#TextView_minWidth
3243 */
3244 public int getMinWidth() {
3245 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3246 }
3247
3248 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003249 * Makes the TextView at most this many ems wide
3250 *
3251 * @attr ref android.R.styleable#TextView_maxEms
3252 */
3253 @android.view.RemotableViewMethod
3254 public void setMaxEms(int maxems) {
3255 mMaxWidth = maxems;
3256 mMaxWidthMode = EMS;
3257
3258 requestLayout();
3259 invalidate();
3260 }
3261
3262 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003263 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3264 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3265 *
3266 * @see #setMaxEms(int)
3267 * @see #setEms(int)
3268 *
3269 * @attr ref android.R.styleable#TextView_maxEms
3270 */
3271 public int getMaxEms() {
3272 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3273 }
3274
3275 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003276 * Makes the TextView at most this many pixels wide
3277 *
3278 * @attr ref android.R.styleable#TextView_maxWidth
3279 */
3280 @android.view.RemotableViewMethod
3281 public void setMaxWidth(int maxpixels) {
3282 mMaxWidth = maxpixels;
3283 mMaxWidthMode = PIXELS;
3284
3285 requestLayout();
3286 invalidate();
3287 }
3288
3289 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003290 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3291 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3292 *
3293 * @see #setMaxWidth(int)
3294 * @see #setWidth(int)
3295 *
3296 * @attr ref android.R.styleable#TextView_maxWidth
3297 */
3298 public int getMaxWidth() {
3299 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3300 }
3301
3302 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003303 * Makes the TextView exactly this many ems wide
3304 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003305 * @see #setMaxEms(int)
3306 * @see #setMinEms(int)
3307 * @see #getMinEms()
3308 * @see #getMaxEms()
3309 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003310 * @attr ref android.R.styleable#TextView_ems
3311 */
3312 @android.view.RemotableViewMethod
3313 public void setEms(int ems) {
3314 mMaxWidth = mMinWidth = ems;
3315 mMaxWidthMode = mMinWidthMode = EMS;
3316
3317 requestLayout();
3318 invalidate();
3319 }
3320
3321 /**
3322 * Makes the TextView exactly this many pixels wide.
3323 * You could do the same thing by specifying this number in the
3324 * LayoutParams.
3325 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003326 * @see #setMaxWidth(int)
3327 * @see #setMinWidth(int)
3328 * @see #getMinWidth()
3329 * @see #getMaxWidth()
3330 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003331 * @attr ref android.R.styleable#TextView_width
3332 */
3333 @android.view.RemotableViewMethod
3334 public void setWidth(int pixels) {
3335 mMaxWidth = mMinWidth = pixels;
3336 mMaxWidthMode = mMinWidthMode = PIXELS;
3337
3338 requestLayout();
3339 invalidate();
3340 }
3341
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003342 /**
3343 * Sets line spacing for this TextView. Each line will have its height
3344 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3345 *
3346 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3347 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3348 */
3349 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003350 if (mSpacingAdd != add || mSpacingMult != mult) {
3351 mSpacingAdd = add;
3352 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353
Gilles Debunne22378292011-08-12 10:38:52 -07003354 if (mLayout != null) {
3355 nullLayouts();
3356 requestLayout();
3357 invalidate();
3358 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003359 }
3360 }
3361
3362 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003363 * Gets the line spacing multiplier
3364 *
3365 * @return the value by which each line's height is multiplied to get its actual height.
3366 *
3367 * @see #setLineSpacing(float, float)
3368 * @see #getLineSpacingExtra()
3369 *
3370 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3371 */
3372 public float getLineSpacingMultiplier() {
3373 return mSpacingMult;
3374 }
3375
3376 /**
3377 * Gets the line spacing extra space
3378 *
3379 * @return the extra space that is added to the height of each lines of this TextView.
3380 *
3381 * @see #setLineSpacing(float, float)
3382 * @see #getLineSpacingMultiplier()
3383 *
3384 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3385 */
3386 public float getLineSpacingExtra() {
3387 return mSpacingAdd;
3388 }
3389
3390 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003391 * Convenience method: Append the specified text to the TextView's
3392 * display buffer, upgrading it to BufferType.EDITABLE if it was
3393 * not already editable.
3394 */
3395 public final void append(CharSequence text) {
3396 append(text, 0, text.length());
3397 }
3398
3399 /**
3400 * Convenience method: Append the specified text slice to the TextView's
3401 * display buffer, upgrading it to BufferType.EDITABLE if it was
3402 * not already editable.
3403 */
3404 public void append(CharSequence text, int start, int end) {
3405 if (!(mText instanceof Editable)) {
3406 setText(mText, BufferType.EDITABLE);
3407 }
3408
3409 ((Editable) mText).append(text, start, end);
3410 }
3411
3412 private void updateTextColors() {
3413 boolean inval = false;
3414 int color = mTextColor.getColorForState(getDrawableState(), 0);
3415 if (color != mCurTextColor) {
3416 mCurTextColor = color;
3417 inval = true;
3418 }
3419 if (mLinkTextColor != null) {
3420 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3421 if (color != mTextPaint.linkColor) {
3422 mTextPaint.linkColor = color;
3423 inval = true;
3424 }
3425 }
3426 if (mHintTextColor != null) {
3427 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3428 if (color != mCurHintTextColor && mText.length() == 0) {
3429 mCurHintTextColor = color;
3430 inval = true;
3431 }
3432 }
3433 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003434 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003435 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003436 invalidate();
3437 }
3438 }
3439
3440 @Override
3441 protected void drawableStateChanged() {
3442 super.drawableStateChanged();
3443 if (mTextColor != null && mTextColor.isStateful()
3444 || (mHintTextColor != null && mHintTextColor.isStateful())
3445 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3446 updateTextColors();
3447 }
3448
3449 final Drawables dr = mDrawables;
3450 if (dr != null) {
3451 int[] state = getDrawableState();
3452 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3453 dr.mDrawableTop.setState(state);
3454 }
3455 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3456 dr.mDrawableBottom.setState(state);
3457 }
3458 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3459 dr.mDrawableLeft.setState(state);
3460 }
3461 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3462 dr.mDrawableRight.setState(state);
3463 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003464 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3465 dr.mDrawableStart.setState(state);
3466 }
3467 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3468 dr.mDrawableEnd.setState(state);
3469 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003470 }
3471 }
3472
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003473 @Override
3474 public Parcelable onSaveInstanceState() {
3475 Parcelable superState = super.onSaveInstanceState();
3476
3477 // Save state if we are forced to
3478 boolean save = mFreezesText;
3479 int start = 0;
3480 int end = 0;
3481
3482 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003483 start = getSelectionStart();
3484 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003485 if (start >= 0 || end >= 0) {
3486 // Or save state if there is a selection
3487 save = true;
3488 }
3489 }
3490
3491 if (save) {
3492 SavedState ss = new SavedState(superState);
3493 // XXX Should also save the current scroll position!
3494 ss.selStart = start;
3495 ss.selEnd = end;
3496
3497 if (mText instanceof Spanned) {
Victoria Leaseaf7dcdf2013-10-24 12:35:42 -07003498 Spannable sp = new SpannableStringBuilder(mText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003499
Gilles Debunne60e21862012-01-30 15:04:14 -08003500 if (mEditor != null) {
3501 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003502 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003503 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003504
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003505 ss.text = sp;
3506 } else {
3507 ss.text = mText.toString();
3508 }
3509
3510 if (isFocused() && start >= 0 && end >= 0) {
3511 ss.frozenWithFocus = true;
3512 }
3513
Gilles Debunne60e21862012-01-30 15:04:14 -08003514 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003515
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003516 return ss;
3517 }
3518
3519 return superState;
3520 }
3521
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003522 void removeMisspelledSpans(Spannable spannable) {
3523 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3524 SuggestionSpan.class);
3525 for (int i = 0; i < suggestionSpans.length; i++) {
3526 int flags = suggestionSpans[i].getFlags();
3527 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3528 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3529 spannable.removeSpan(suggestionSpans[i]);
3530 }
3531 }
3532 }
3533
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003534 @Override
3535 public void onRestoreInstanceState(Parcelable state) {
3536 if (!(state instanceof SavedState)) {
3537 super.onRestoreInstanceState(state);
3538 return;
3539 }
3540
3541 SavedState ss = (SavedState)state;
3542 super.onRestoreInstanceState(ss.getSuperState());
3543
3544 // XXX restore buffer type too, as well as lots of other stuff
3545 if (ss.text != null) {
3546 setText(ss.text);
3547 }
3548
3549 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3550 if (mText instanceof Spannable) {
3551 int len = mText.length();
3552
3553 if (ss.selStart > len || ss.selEnd > len) {
3554 String restored = "";
3555
3556 if (ss.text != null) {
3557 restored = "(restored) ";
3558 }
3559
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003560 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003561 "/" + ss.selEnd + " out of range for " + restored +
3562 "text " + mText);
3563 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003564 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003565
3566 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003567 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003568 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003569 }
3570 }
3571 }
3572 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003573
3574 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003575 final CharSequence error = ss.error;
3576 // Display the error later, after the first layout pass
3577 post(new Runnable() {
3578 public void run() {
3579 setError(error);
3580 }
3581 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003582 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003583 }
3584
3585 /**
3586 * Control whether this text view saves its entire text contents when
3587 * freezing to an icicle, in addition to dynamic state such as cursor
3588 * position. By default this is false, not saving the text. Set to true
3589 * if the text in the text view is not being saved somewhere else in
3590 * persistent storage (such as in a content provider) so that if the
3591 * view is later thawed the user will not lose their data.
3592 *
3593 * @param freezesText Controls whether a frozen icicle should include the
3594 * entire text data: true to include it, false to not.
3595 *
3596 * @attr ref android.R.styleable#TextView_freezesText
3597 */
3598 @android.view.RemotableViewMethod
3599 public void setFreezesText(boolean freezesText) {
3600 mFreezesText = freezesText;
3601 }
3602
3603 /**
3604 * Return whether this text view is including its entire text contents
3605 * in frozen icicles.
3606 *
3607 * @return Returns true if text is included, false if it isn't.
3608 *
3609 * @see #setFreezesText
3610 */
3611 public boolean getFreezesText() {
3612 return mFreezesText;
3613 }
3614
3615 ///////////////////////////////////////////////////////////////////////////
3616
3617 /**
3618 * Sets the Factory used to create new Editables.
3619 */
3620 public final void setEditableFactory(Editable.Factory factory) {
3621 mEditableFactory = factory;
3622 setText(mText);
3623 }
3624
3625 /**
3626 * Sets the Factory used to create new Spannables.
3627 */
3628 public final void setSpannableFactory(Spannable.Factory factory) {
3629 mSpannableFactory = factory;
3630 setText(mText);
3631 }
3632
3633 /**
3634 * Sets the string value of the TextView. TextView <em>does not</em> accept
3635 * HTML-like formatting, which you can do with text strings in XML resource files.
3636 * To style your strings, attach android.text.style.* objects to a
3637 * {@link android.text.SpannableString SpannableString}, or see the
3638 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003639 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003640 * formatted text in the XML resource file.
3641 *
3642 * @attr ref android.R.styleable#TextView_text
3643 */
3644 @android.view.RemotableViewMethod
3645 public final void setText(CharSequence text) {
3646 setText(text, mBufferType);
3647 }
3648
3649 /**
3650 * Like {@link #setText(CharSequence)},
3651 * except that the cursor position (if any) is retained in the new text.
3652 *
3653 * @param text The new text to place in the text view.
3654 *
3655 * @see #setText(CharSequence)
3656 */
3657 @android.view.RemotableViewMethod
3658 public final void setTextKeepState(CharSequence text) {
3659 setTextKeepState(text, mBufferType);
3660 }
3661
3662 /**
3663 * Sets the text that this TextView is to display (see
3664 * {@link #setText(CharSequence)}) and also sets whether it is stored
3665 * in a styleable/spannable buffer and whether it is editable.
3666 *
3667 * @attr ref android.R.styleable#TextView_text
3668 * @attr ref android.R.styleable#TextView_bufferType
3669 */
3670 public void setText(CharSequence text, BufferType type) {
3671 setText(text, type, true, 0);
3672
3673 if (mCharWrapper != null) {
3674 mCharWrapper.mChars = null;
3675 }
3676 }
3677
3678 private void setText(CharSequence text, BufferType type,
3679 boolean notifyBefore, int oldlen) {
3680 if (text == null) {
3681 text = "";
3682 }
3683
Luca Zanoline0760452011-09-08 12:03:37 +01003684 // If suggestions are not enabled, remove the suggestion spans from the text
3685 if (!isSuggestionsEnabled()) {
3686 text = removeSuggestionSpans(text);
3687 }
3688
Romain Guy939151f2009-04-08 14:22:40 -07003689 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3690
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003691 if (text instanceof Spanned &&
3692 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003693 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3694 setHorizontalFadingEdgeEnabled(true);
3695 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3696 } else {
3697 setHorizontalFadingEdgeEnabled(false);
3698 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3699 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003700 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3701 }
3702
3703 int n = mFilters.length;
3704 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003705 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003706 if (out != null) {
3707 text = out;
3708 }
3709 }
3710
3711 if (notifyBefore) {
3712 if (mText != null) {
3713 oldlen = mText.length();
3714 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3715 } else {
3716 sendBeforeTextChanged("", 0, 0, text.length());
3717 }
3718 }
3719
3720 boolean needEditableForNotification = false;
3721
3722 if (mListeners != null && mListeners.size() != 0) {
3723 needEditableForNotification = true;
3724 }
3725
Gilles Debunne2d373a12012-04-20 15:32:19 -07003726 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3727 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003728 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003729 Editable t = mEditableFactory.newEditable(text);
3730 text = t;
3731 setFilters(t, mFilters);
3732 InputMethodManager imm = InputMethodManager.peekInstance();
3733 if (imm != null) imm.restartInput(this);
3734 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3735 text = mSpannableFactory.newSpannable(text);
3736 } else if (!(text instanceof CharWrapper)) {
3737 text = TextUtils.stringOrSpannedString(text);
3738 }
3739
3740 if (mAutoLinkMask != 0) {
3741 Spannable s2;
3742
3743 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3744 s2 = (Spannable) text;
3745 } else {
3746 s2 = mSpannableFactory.newSpannable(text);
3747 }
3748
3749 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3750 text = s2;
3751 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3752
3753 /*
3754 * We must go ahead and set the text before changing the
3755 * movement method, because setMovementMethod() may call
3756 * setText() again to try to upgrade the buffer type.
3757 */
3758 mText = text;
3759
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003760 // Do not change the movement method for text that support text selection as it
3761 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003762 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003763 setMovementMethod(LinkMovementMethod.getInstance());
3764 }
3765 }
3766 }
3767
3768 mBufferType = type;
3769 mText = text;
3770
Adam Powell7f8f79a2011-07-07 18:35:54 -07003771 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003772 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003773 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003774 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003775 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003776
3777 final int textLength = text.length();
3778
Adam Powell7f8f79a2011-07-07 18:35:54 -07003779 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003780 Spannable sp = (Spannable) text;
3781
Gilles Debunnec62589c2012-04-12 14:50:23 -07003782 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003783 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3784 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003785 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003786 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003787 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003788
Gilles Debunnec62589c2012-04-12 14:50:23 -07003789 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003790
3791 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003792 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003793
Gilles Debunnec62589c2012-04-12 14:50:23 -07003794 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003795
3796 if (mTransformation != null) {
3797 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003798 }
3799
3800 if (mMovement != null) {
3801 mMovement.initialize(this, (Spannable) text);
3802
3803 /*
3804 * Initializing the movement method will have set the
3805 * selection, so reset mSelectionMoved to keep that from
3806 * interfering with the normal on-focus selection-setting.
3807 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003808 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003809 }
3810 }
3811
3812 if (mLayout != null) {
3813 checkForRelayout();
3814 }
3815
3816 sendOnTextChanged(text, 0, oldlen, textLength);
3817 onTextChanged(text, 0, oldlen, textLength);
3818
Alan Viverette77e9a282013-09-12 17:16:09 -07003819 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
3820
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003821 if (needEditableForNotification) {
3822 sendAfterTextChanged((Editable) text);
3823 }
Gilles Debunne05336272010-07-09 20:13:45 -07003824
Gilles Debunnebaaace52010-10-01 15:47:13 -07003825 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003826 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003827 }
3828
3829 /**
3830 * Sets the TextView to display the specified slice of the specified
3831 * char array. You must promise that you will not change the contents
3832 * of the array except for right before another call to setText(),
3833 * since the TextView has no way to know that the text
3834 * has changed and that it needs to invalidate and re-layout.
3835 */
3836 public final void setText(char[] text, int start, int len) {
3837 int oldlen = 0;
3838
3839 if (start < 0 || len < 0 || start + len > text.length) {
3840 throw new IndexOutOfBoundsException(start + ", " + len);
3841 }
3842
3843 /*
3844 * We must do the before-notification here ourselves because if
3845 * the old text is a CharWrapper we destroy it before calling
3846 * into the normal path.
3847 */
3848 if (mText != null) {
3849 oldlen = mText.length();
3850 sendBeforeTextChanged(mText, 0, oldlen, len);
3851 } else {
3852 sendBeforeTextChanged("", 0, 0, len);
3853 }
3854
3855 if (mCharWrapper == null) {
3856 mCharWrapper = new CharWrapper(text, start, len);
3857 } else {
3858 mCharWrapper.set(text, start, len);
3859 }
3860
3861 setText(mCharWrapper, mBufferType, false, oldlen);
3862 }
3863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003864 /**
3865 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3866 * except that the cursor position (if any) is retained in the new text.
3867 *
3868 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3869 */
3870 public final void setTextKeepState(CharSequence text, BufferType type) {
3871 int start = getSelectionStart();
3872 int end = getSelectionEnd();
3873 int len = text.length();
3874
3875 setText(text, type);
3876
3877 if (start >= 0 || end >= 0) {
3878 if (mText instanceof Spannable) {
3879 Selection.setSelection((Spannable) mText,
3880 Math.max(0, Math.min(start, len)),
3881 Math.max(0, Math.min(end, len)));
3882 }
3883 }
3884 }
3885
3886 @android.view.RemotableViewMethod
3887 public final void setText(int resid) {
3888 setText(getContext().getResources().getText(resid));
3889 }
3890
3891 public final void setText(int resid, BufferType type) {
3892 setText(getContext().getResources().getText(resid), type);
3893 }
3894
3895 /**
3896 * Sets the text to be displayed when the text of the TextView is empty.
3897 * Null means to use the normal empty text. The hint does not currently
3898 * participate in determining the size of the view.
3899 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003900 * @attr ref android.R.styleable#TextView_hint
3901 */
3902 @android.view.RemotableViewMethod
3903 public final void setHint(CharSequence hint) {
3904 mHint = TextUtils.stringOrSpannedString(hint);
3905
3906 if (mLayout != null) {
3907 checkForRelayout();
3908 }
3909
Romain Guy4dc4f732009-06-19 15:16:40 -07003910 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003911 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003912 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003913
Gilles Debunne33b7de852012-03-12 11:57:48 -07003914 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003915 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003916 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003917 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003918 }
3919
3920 /**
3921 * Sets the text to be displayed when the text of the TextView is empty,
3922 * from a resource.
3923 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003924 * @attr ref android.R.styleable#TextView_hint
3925 */
3926 @android.view.RemotableViewMethod
3927 public final void setHint(int resid) {
3928 setHint(getContext().getResources().getText(resid));
3929 }
3930
3931 /**
3932 * Returns the hint that is displayed when the text of the TextView
3933 * is empty.
3934 *
3935 * @attr ref android.R.styleable#TextView_hint
3936 */
3937 @ViewDebug.CapturedViewProperty
3938 public CharSequence getHint() {
3939 return mHint;
3940 }
3941
Gilles Debunned88876a2012-03-16 17:34:04 -07003942 boolean isSingleLine() {
3943 return mSingleLine;
3944 }
3945
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003946 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003947 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3948 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3949 }
3950
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003951 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003952 * Removes the suggestion spans.
3953 */
3954 CharSequence removeSuggestionSpans(CharSequence text) {
3955 if (text instanceof Spanned) {
3956 Spannable spannable;
3957 if (text instanceof Spannable) {
3958 spannable = (Spannable) text;
3959 } else {
3960 spannable = new SpannableString(text);
3961 text = spannable;
3962 }
3963
3964 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3965 for (int i = 0; i < spans.length; i++) {
3966 spannable.removeSpan(spans[i]);
3967 }
3968 }
3969 return text;
3970 }
3971
3972 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003973 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3974 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3975 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3976 * then a soft keyboard will not be displayed for this text view.
3977 *
3978 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3979 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3980 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003981 *
3982 * @see #getInputType()
3983 * @see #setRawInputType(int)
3984 * @see android.text.InputType
3985 * @attr ref android.R.styleable#TextView_inputType
3986 */
3987 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003988 final boolean wasPassword = isPasswordInputType(getInputType());
3989 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003990 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003991 final boolean isPassword = isPasswordInputType(type);
3992 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003993 boolean forceUpdate = false;
3994 if (isPassword) {
3995 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07003996 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003997 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07003998 if (mTransformation == PasswordTransformationMethod.getInstance()) {
3999 forceUpdate = true;
4000 }
Raph Leviend570e892012-05-09 11:45:34 -07004001 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004002 } else if (wasPassword || wasVisiblePassword) {
4003 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004004 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004005 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4006 forceUpdate = true;
4007 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004008 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004009
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004010 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004012 // We need to update the single line mode if it has changed or we
4013 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004014 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004015 // Change single line mode, but only change the transformation if
4016 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004017 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004018 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004019
Luca Zanoline0760452011-09-08 12:03:37 +01004020 if (!isSuggestionsEnabled()) {
4021 mText = removeSuggestionSpans(mText);
4022 }
4023
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004024 InputMethodManager imm = InputMethodManager.peekInstance();
4025 if (imm != null) imm.restartInput(this);
4026 }
4027
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004028 /**
4029 * It would be better to rely on the input type for everything. A password inputType should have
4030 * a password transformation. We should hence use isPasswordInputType instead of this method.
4031 *
4032 * We should:
4033 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4034 * would install the correct transformation).
4035 * - Refuse the installation of a non-password transformation in setTransformation if the input
4036 * type is password.
4037 *
4038 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4039 * is useful since it matches what the user can see (obfuscated text or not).
4040 *
4041 * @return true if the current transformation method is of the password type.
4042 */
4043 private boolean hasPasswordTransformationMethod() {
4044 return mTransformation instanceof PasswordTransformationMethod;
4045 }
4046
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004047 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004048 final int variation =
4049 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004050 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004051 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4052 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004053 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4054 || variation
4055 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004056 }
4057
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004058 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004059 final int variation =
4060 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004061 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004062 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004063 }
4064
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004065 /**
4066 * Directly change the content type integer of the text view, without
4067 * modifying any other state.
4068 * @see #setInputType(int)
4069 * @see android.text.InputType
4070 * @attr ref android.R.styleable#TextView_inputType
4071 */
4072 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004073 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004074 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004075 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004076 }
4077
4078 private void setInputType(int type, boolean direct) {
4079 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4080 KeyListener input;
4081 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004082 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004083 TextKeyListener.Capitalize cap;
4084 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4085 cap = TextKeyListener.Capitalize.CHARACTERS;
4086 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4087 cap = TextKeyListener.Capitalize.WORDS;
4088 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4089 cap = TextKeyListener.Capitalize.SENTENCES;
4090 } else {
4091 cap = TextKeyListener.Capitalize.NONE;
4092 }
4093 input = TextKeyListener.getInstance(autotext, cap);
4094 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4095 input = DigitsKeyListener.getInstance(
4096 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4097 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4098 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4099 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4100 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4101 input = DateKeyListener.getInstance();
4102 break;
4103 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4104 input = TimeKeyListener.getInstance();
4105 break;
4106 default:
4107 input = DateTimeKeyListener.getInstance();
4108 break;
4109 }
4110 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4111 input = DialerKeyListener.getInstance();
4112 } else {
4113 input = TextKeyListener.getInstance();
4114 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004115 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004116 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004117 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004118 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004119 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004120 setKeyListenerOnly(input);
4121 }
4122 }
4123
4124 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004125 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004126 *
4127 * @see #setInputType(int)
4128 * @see android.text.InputType
4129 */
4130 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004131 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004132 }
4133
4134 /**
4135 * Change the editor type integer associated with the text view, which
4136 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4137 * has focus.
4138 * @see #getImeOptions
4139 * @see android.view.inputmethod.EditorInfo
4140 * @attr ref android.R.styleable#TextView_imeOptions
4141 */
4142 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004143 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004144 mEditor.createInputContentTypeIfNeeded();
4145 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004146 }
4147
4148 /**
4149 * Get the type of the IME editor.
4150 *
4151 * @see #setImeOptions(int)
4152 * @see android.view.inputmethod.EditorInfo
4153 */
4154 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004155 return mEditor != null && mEditor.mInputContentType != null
4156 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004157 }
4158
4159 /**
4160 * Change the custom IME action associated with the text view, which
4161 * will be reported to an IME with {@link EditorInfo#actionLabel}
4162 * and {@link EditorInfo#actionId} when it has focus.
4163 * @see #getImeActionLabel
4164 * @see #getImeActionId
4165 * @see android.view.inputmethod.EditorInfo
4166 * @attr ref android.R.styleable#TextView_imeActionLabel
4167 * @attr ref android.R.styleable#TextView_imeActionId
4168 */
4169 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004170 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004171 mEditor.createInputContentTypeIfNeeded();
4172 mEditor.mInputContentType.imeActionLabel = label;
4173 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004174 }
4175
4176 /**
4177 * Get the IME action label previous set with {@link #setImeActionLabel}.
4178 *
4179 * @see #setImeActionLabel
4180 * @see android.view.inputmethod.EditorInfo
4181 */
4182 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004183 return mEditor != null && mEditor.mInputContentType != null
4184 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004185 }
4186
4187 /**
4188 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4189 *
4190 * @see #setImeActionLabel
4191 * @see android.view.inputmethod.EditorInfo
4192 */
4193 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004194 return mEditor != null && mEditor.mInputContentType != null
4195 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004196 }
4197
4198 /**
4199 * Set a special listener to be called when an action is performed
4200 * on the text view. This will be called when the enter key is pressed,
4201 * or when an action supplied to the IME is selected by the user. Setting
4202 * this means that the normal hard key event will not insert a newline
4203 * into the text view, even if it is multi-line; holding down the ALT
4204 * modifier will, however, allow the user to insert a newline character.
4205 */
4206 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004207 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004208 mEditor.createInputContentTypeIfNeeded();
4209 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004210 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004211
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004212 /**
4213 * Called when an attached input method calls
4214 * {@link InputConnection#performEditorAction(int)
4215 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004216 * for this text view. The default implementation will call your action
4217 * listener supplied to {@link #setOnEditorActionListener}, or perform
4218 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004219 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4220 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004221 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004222 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004223 * <p>For backwards compatibility, if no IME options have been set and the
4224 * text view would not normally advance focus on enter, then
4225 * the NEXT and DONE actions received here will be turned into an enter
4226 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004227 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004228 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004229 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004230 * @see #setOnEditorActionListener
4231 */
4232 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004233 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004234 if (ict != null) {
4235 if (ict.onEditorActionListener != null) {
4236 if (ict.onEditorActionListener.onEditorAction(this,
4237 actionCode, null)) {
4238 return;
4239 }
4240 }
Gilles Debunne64794482011-11-30 15:45:28 -08004241
The Android Open Source Project4df24232009-03-05 14:34:35 -08004242 // This is the handling for some default action.
4243 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004244 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004245 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004246 // app may be expecting.
4247 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004248 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004249 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004250 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004251 throw new IllegalStateException("focus search returned a view " +
4252 "that wasn't able to take focus!");
4253 }
4254 }
4255 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004256
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004257 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004258 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004259 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004260 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004261 throw new IllegalStateException("focus search returned a view " +
4262 "that wasn't able to take focus!");
4263 }
4264 }
4265 return;
4266
The Android Open Source Project4df24232009-03-05 14:34:35 -08004267 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4268 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004269 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004270 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004271 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004272 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004273 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004274 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004275
Jeff Browna175a5b2012-02-15 19:18:31 -08004276 ViewRootImpl viewRootImpl = getViewRootImpl();
4277 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004278 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004279 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004280 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004281 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4282 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004283 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004284 | KeyEvent.FLAG_EDITOR_ACTION));
4285 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004286 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004287 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4288 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004289 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004290 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004291 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004292 }
Gilles Debunne64794482011-11-30 15:45:28 -08004293
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004294 /**
4295 * Set the private content type of the text, which is the
4296 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4297 * field that will be filled in when creating an input connection.
4298 *
4299 * @see #getPrivateImeOptions()
4300 * @see EditorInfo#privateImeOptions
4301 * @attr ref android.R.styleable#TextView_privateImeOptions
4302 */
4303 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004304 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004305 mEditor.createInputContentTypeIfNeeded();
4306 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004307 }
4308
4309 /**
4310 * Get the private type of the content.
4311 *
4312 * @see #setPrivateImeOptions(String)
4313 * @see EditorInfo#privateImeOptions
4314 */
4315 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004316 return mEditor != null && mEditor.mInputContentType != null
4317 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004318 }
4319
4320 /**
4321 * Set the extra input data of the text, which is the
4322 * {@link EditorInfo#extras TextBoxAttribute.extras}
4323 * Bundle that will be filled in when creating an input connection. The
4324 * given integer is the resource ID of an XML resource holding an
4325 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4326 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004327 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004328 * @see EditorInfo#extras
4329 * @attr ref android.R.styleable#TextView_editorExtras
4330 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004331 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004332 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004333 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004334 mEditor.createInputContentTypeIfNeeded();
4335 mEditor.mInputContentType.extras = new Bundle();
4336 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004337 }
4338
4339 /**
4340 * Retrieve the input extras currently associated with the text view, which
4341 * can be viewed as well as modified.
4342 *
4343 * @param create If true, the extras will be created if they don't already
4344 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004345 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004346 * @see EditorInfo#extras
4347 * @attr ref android.R.styleable#TextView_editorExtras
4348 */
4349 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004350 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004351 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004352 if (mEditor.mInputContentType == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004353 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004354 mEditor.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004355 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004356 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004357 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004358 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004359 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004360 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004361 }
4362
4363 /**
4364 * Returns the error message that was set to be displayed with
4365 * {@link #setError}, or <code>null</code> if no error was set
4366 * or if it the error was cleared by the widget after user input.
4367 */
4368 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004369 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004370 }
4371
4372 /**
4373 * Sets the right-hand compound drawable of the TextView to the "error"
4374 * icon and sets an error message that will be displayed in a popup when
4375 * the TextView has focus. The icon and error message will be reset to
4376 * null when any key events cause changes to the TextView's text. If the
4377 * <code>error</code> is <code>null</code>, the error message and icon
4378 * will be cleared.
4379 */
4380 @android.view.RemotableViewMethod
4381 public void setError(CharSequence error) {
4382 if (error == null) {
4383 setError(null, null);
4384 } else {
4385 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08004386 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004387
4388 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4389 setError(error, dr);
4390 }
4391 }
4392
4393 /**
4394 * Sets the right-hand compound drawable of the TextView to the specified
4395 * icon and sets an error message that will be displayed in a popup when
4396 * the TextView has focus. The icon and error message will be reset to
4397 * null when any key events cause changes to the TextView's text. The
4398 * drawable must already have had {@link Drawable#setBounds} set on it.
4399 * If the <code>error</code> is <code>null</code>, the error message will
4400 * be cleared (and you should provide a <code>null</code> icon as well).
4401 */
4402 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004403 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004404 mEditor.setError(error, icon);
Alan Viverette77e9a282013-09-12 17:16:09 -07004405 notifyViewAccessibilityStateChangedIfNeeded(
4406 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004407 }
4408
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004409 @Override
4410 protected boolean setFrame(int l, int t, int r, int b) {
4411 boolean result = super.setFrame(l, t, r, b);
4412
Gilles Debunne2d373a12012-04-20 15:32:19 -07004413 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004414
Romain Guy986003d2009-03-25 17:42:35 -07004415 restartMarqueeIfNeeded();
4416
4417 return result;
4418 }
4419
4420 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004421 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4422 mRestartMarquee = false;
4423 startMarquee();
4424 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004425 }
4426
4427 /**
4428 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004429 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004430 *
4431 * @attr ref android.R.styleable#TextView_maxLength
4432 */
4433 public void setFilters(InputFilter[] filters) {
4434 if (filters == null) {
4435 throw new IllegalArgumentException();
4436 }
4437
4438 mFilters = filters;
4439
4440 if (mText instanceof Editable) {
4441 setFilters((Editable) mText, filters);
4442 }
4443 }
4444
4445 /**
4446 * Sets the list of input filters on the specified Editable,
4447 * and includes mInput in the list if it is an InputFilter.
4448 */
4449 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004450 if (mEditor != null) {
4451 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4452 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4453 int num = 0;
4454 if (undoFilter) num++;
4455 if (keyFilter) num++;
4456 if (num > 0) {
4457 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004458
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004459 System.arraycopy(filters, 0, nf, 0, filters.length);
4460 num = 0;
4461 if (undoFilter) {
4462 nf[filters.length] = mEditor.mUndoInputFilter;
4463 num++;
4464 }
4465 if (keyFilter) {
4466 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4467 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004468
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004469 e.setFilters(nf);
4470 return;
4471 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004472 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004473 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004474 }
4475
4476 /**
4477 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004478 *
4479 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004480 */
4481 public InputFilter[] getFilters() {
4482 return mFilters;
4483 }
4484
4485 /////////////////////////////////////////////////////////////////////////
4486
Philip Milne7b757812012-09-19 18:13:44 -07004487 private int getBoxHeight(Layout l) {
4488 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4489 int padding = (l == mHintLayout) ?
4490 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4491 getExtendedPaddingTop() + getExtendedPaddingBottom();
4492 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4493 }
4494
Gilles Debunned88876a2012-03-16 17:34:04 -07004495 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004496 int voffset = 0;
4497 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4498
4499 Layout l = mLayout;
4500 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4501 l = mHintLayout;
4502 }
4503
4504 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07004505 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004506 int textht = l.getHeight();
4507
4508 if (textht < boxht) {
4509 if (gravity == Gravity.BOTTOM)
4510 voffset = boxht - textht;
4511 else // (gravity == Gravity.CENTER_VERTICAL)
4512 voffset = (boxht - textht) >> 1;
4513 }
4514 }
4515 return voffset;
4516 }
4517
4518 private int getBottomVerticalOffset(boolean forceNormal) {
4519 int voffset = 0;
4520 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4521
4522 Layout l = mLayout;
4523 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4524 l = mHintLayout;
4525 }
4526
4527 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07004528 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004529 int textht = l.getHeight();
4530
4531 if (textht < boxht) {
4532 if (gravity == Gravity.TOP)
4533 voffset = boxht - textht;
4534 else // (gravity == Gravity.CENTER_VERTICAL)
4535 voffset = (boxht - textht) >> 1;
4536 }
4537 }
4538 return voffset;
4539 }
4540
Gilles Debunned88876a2012-03-16 17:34:04 -07004541 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004542 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004543 invalidateCursor();
4544 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004545 final int horizontalPadding = getCompoundPaddingLeft();
4546 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004547
Gilles Debunne2d373a12012-04-20 15:32:19 -07004548 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004549 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004550 /*
4551 * The reason for this concern about the thickness of the
4552 * cursor and doing the floor/ceil on the coordinates is that
4553 * some EditTexts (notably textfields in the Browser) have
4554 * anti-aliased text where not all the characters are
4555 * necessarily at integer-multiple locations. This should
4556 * make sure the entire cursor gets invalidated instead of
4557 * sometimes missing half a pixel.
4558 */
4559 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4560 if (thick < 1.0f) {
4561 thick = 1.0f;
4562 }
4563
4564 thick /= 2.0f;
4565
Gilles Debunne83051b82012-02-24 20:01:13 -08004566 // mHighlightPath is guaranteed to be non null at that point.
4567 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004568
Gilles Debunne60e21862012-01-30 15:04:14 -08004569 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4570 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4571 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4572 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004573 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004574 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004575 for (int i = 0; i < mEditor.mCursorCount; i++) {
4576 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004577 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4578 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004580 }
4581 }
4582 }
4583
Gilles Debunned88876a2012-03-16 17:34:04 -07004584 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004585 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004586
4587 invalidateCursor(where, where, where);
4588 }
4589
4590 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004591 if (a >= 0 || b >= 0 || c >= 0) {
4592 int start = Math.min(Math.min(a, b), c);
4593 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004594 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004595 }
4596 }
4597
4598 /**
4599 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004600 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004601 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004602 if (mLayout == null) {
4603 invalidate();
4604 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004605 int lineStart = mLayout.getLineForOffset(start);
4606 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004607
4608 // This is ridiculous, but the descent from the line above
4609 // can hang down into the line we really want to redraw,
4610 // so we have to invalidate part of the line above to make
4611 // sure everything that needs to be redrawn really is.
4612 // (But not the whole line above, because that would cause
4613 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004614 if (lineStart > 0) {
4615 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004616 }
4617
Gilles Debunne8615ac92011-11-29 15:25:03 -08004618 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004619
Gilles Debunne8615ac92011-11-29 15:25:03 -08004620 if (start == end)
4621 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004622 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004623 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004624
Gilles Debunne8615ac92011-11-29 15:25:03 -08004625 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004626
Gilles Debunne83051b82012-02-24 20:01:13 -08004627 // mEditor can be null in case selection is set programmatically.
4628 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004629 for (int i = 0; i < mEditor.mCursorCount; i++) {
4630 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004631 top = Math.min(top, bounds.top);
4632 bottom = Math.max(bottom, bounds.bottom);
4633 }
4634 }
4635
Gilles Debunne8615ac92011-11-29 15:25:03 -08004636 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004637 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004638
4639 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004640 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004641 left = (int) mLayout.getPrimaryHorizontal(start);
4642 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4643 left += compoundPaddingLeft;
4644 right += compoundPaddingLeft;
4645 } else {
4646 // Rectangle bounding box when the region spans several lines
4647 left = compoundPaddingLeft;
4648 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004649 }
4650
Gilles Debunne8615ac92011-11-29 15:25:03 -08004651 invalidate(mScrollX + left, verticalPadding + top,
4652 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004653 }
4654 }
4655
4656 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004657 if (!mPreDrawRegistered) {
4658 getViewTreeObserver().addOnPreDrawListener(this);
4659 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004660 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004661 }
4662
4663 /**
4664 * {@inheritDoc}
4665 */
4666 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004667 if (mLayout == null) {
4668 assumeLayout();
4669 }
4670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004671 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004672 /* This code also provides auto-scrolling when a cursor is moved using a
4673 * CursorController (insertion point or selection limits).
4674 * For selection, ensure start or end is visible depending on controller's state.
4675 */
4676 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004677 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004678 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4679 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004680 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004681 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004682
4683 /*
4684 * TODO: This should really only keep the end in view if
4685 * it already was before the text changed. I'm not sure
4686 * of a good way to tell from here if it was.
4687 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004688 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004689 curs = mText.length();
4690 }
4691
4692 if (curs >= 0) {
Raph Leviene048f842013-09-27 13:36:24 -07004693 bringPointIntoView(curs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004694 }
4695 } else {
Raph Leviene048f842013-09-27 13:36:24 -07004696 bringTextIntoView();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004697 }
4698
Gilles Debunne64e54a62010-09-07 19:07:17 -07004699 // This has to be checked here since:
4700 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4701 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004702 if (mEditor != null && mEditor.mCreatedWithASelection) {
4703 mEditor.startSelectionActionMode();
4704 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004705 }
4706
4707 // Phone specific code (there is no ExtractEditText on tablets).
4708 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4709 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004710 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004711 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004712 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004713
Gilles Debunne2e37d622012-01-27 13:54:00 -08004714 getViewTreeObserver().removeOnPreDrawListener(this);
4715 mPreDrawRegistered = false;
4716
Raph Leviene048f842013-09-27 13:36:24 -07004717 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004718 }
4719
4720 @Override
4721 protected void onAttachedToWindow() {
4722 super.onAttachedToWindow();
4723
4724 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004725
Gilles Debunne2d373a12012-04-20 15:32:19 -07004726 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004727 }
4728
4729 @Override
4730 protected void onDetachedFromWindow() {
4731 super.onDetachedFromWindow();
4732
Gilles Debunne2e37d622012-01-27 13:54:00 -08004733 if (mPreDrawRegistered) {
4734 getViewTreeObserver().removeOnPreDrawListener(this);
4735 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004736 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004737
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004738 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004739
Gilles Debunne2d373a12012-04-20 15:32:19 -07004740 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004741 }
4742
4743 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004744 public void onScreenStateChanged(int screenState) {
4745 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004746 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004747 }
4748
4749 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004750 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004751 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004752 }
4753
4754 @Override
4755 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004756 return getCompoundPaddingLeft() - mPaddingLeft +
4757 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004758 }
4759
4760 @Override
4761 protected int getTopPaddingOffset() {
4762 return (int) Math.min(0, mShadowDy - mShadowRadius);
4763 }
4764
4765 @Override
4766 protected int getBottomPaddingOffset() {
4767 return (int) Math.max(0, mShadowDy + mShadowRadius);
4768 }
4769
4770 @Override
4771 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004772 return -(getCompoundPaddingRight() - mPaddingRight) +
4773 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004774 }
4775
4776 @Override
4777 protected boolean verifyDrawable(Drawable who) {
4778 final boolean verified = super.verifyDrawable(who);
4779 if (!verified && mDrawables != null) {
4780 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004781 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4782 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004783 }
4784 return verified;
4785 }
4786
4787 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004788 public void jumpDrawablesToCurrentState() {
4789 super.jumpDrawablesToCurrentState();
4790 if (mDrawables != null) {
4791 if (mDrawables.mDrawableLeft != null) {
4792 mDrawables.mDrawableLeft.jumpToCurrentState();
4793 }
4794 if (mDrawables.mDrawableTop != null) {
4795 mDrawables.mDrawableTop.jumpToCurrentState();
4796 }
4797 if (mDrawables.mDrawableRight != null) {
4798 mDrawables.mDrawableRight.jumpToCurrentState();
4799 }
4800 if (mDrawables.mDrawableBottom != null) {
4801 mDrawables.mDrawableBottom.jumpToCurrentState();
4802 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004803 if (mDrawables.mDrawableStart != null) {
4804 mDrawables.mDrawableStart.jumpToCurrentState();
4805 }
4806 if (mDrawables.mDrawableEnd != null) {
4807 mDrawables.mDrawableEnd.jumpToCurrentState();
4808 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004809 }
4810 }
4811
4812 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004813 public void invalidateDrawable(Drawable drawable) {
4814 if (verifyDrawable(drawable)) {
4815 final Rect dirty = drawable.getBounds();
4816 int scrollX = mScrollX;
4817 int scrollY = mScrollY;
4818
4819 // IMPORTANT: The coordinates below are based on the coordinates computed
4820 // for each compound drawable in onDraw(). Make sure to update each section
4821 // accordingly.
4822 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004823 if (drawables != null) {
4824 if (drawable == drawables.mDrawableLeft) {
4825 final int compoundPaddingTop = getCompoundPaddingTop();
4826 final int compoundPaddingBottom = getCompoundPaddingBottom();
4827 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004828
Romain Guya6cd4e02009-05-20 15:09:21 -07004829 scrollX += mPaddingLeft;
4830 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4831 } else if (drawable == drawables.mDrawableRight) {
4832 final int compoundPaddingTop = getCompoundPaddingTop();
4833 final int compoundPaddingBottom = getCompoundPaddingBottom();
4834 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004835
Romain Guya6cd4e02009-05-20 15:09:21 -07004836 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4837 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4838 } else if (drawable == drawables.mDrawableTop) {
4839 final int compoundPaddingLeft = getCompoundPaddingLeft();
4840 final int compoundPaddingRight = getCompoundPaddingRight();
4841 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004842
Romain Guya6cd4e02009-05-20 15:09:21 -07004843 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4844 scrollY += mPaddingTop;
4845 } else if (drawable == drawables.mDrawableBottom) {
4846 final int compoundPaddingLeft = getCompoundPaddingLeft();
4847 final int compoundPaddingRight = getCompoundPaddingRight();
4848 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004849
Romain Guya6cd4e02009-05-20 15:09:21 -07004850 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4851 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4852 }
Romain Guy3c77d392009-05-20 11:26:50 -07004853 }
4854
4855 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4856 dirty.right + scrollX, dirty.bottom + scrollY);
4857 }
4858 }
4859
4860 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004861 public boolean hasOverlappingRendering() {
Chris Craik7bcde502013-10-11 12:51:11 -07004862 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
Michael Jurka0931a852013-03-21 16:07:45 +01004863 return ((getBackground() != null && getBackground().getCurrent() != null)
Chris Craik7bcde502013-10-11 12:51:11 -07004864 || mText instanceof Spannable || hasSelection()
4865 || isHorizontalFadingEdgeEnabled());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004866 }
4867
Gilles Debunne86b9c782010-11-11 10:43:48 -08004868 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08004869 *
Joe Malin10d96952013-05-29 17:49:09 -07004870 * Returns the state of the {@code textIsSelectable} flag (See
4871 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
4872 * to allow users to select and copy text in a non-editable TextView, the content of an
4873 * {@link EditText} can always be selected, independently of the value of this flag.
4874 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08004875 *
4876 * @return True if the text displayed in this TextView can be selected by the user.
4877 *
4878 * @attr ref android.R.styleable#TextView_textIsSelectable
4879 */
4880 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004881 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004882 }
4883
4884 /**
Joe Malin10d96952013-05-29 17:49:09 -07004885 * Sets whether the content of this view is selectable by the user. The default is
4886 * {@code false}, meaning that the content is not selectable.
4887 * <p>
4888 * When you use a TextView to display a useful piece of information to the user (such as a
4889 * contact's address), make it selectable, so that the user can select and copy its
4890 * content. You can also use set the XML attribute
4891 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
4892 * <p>
4893 * When you call this method to set the value of {@code textIsSelectable}, it sets
4894 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
4895 * and {@code longClickable} to the same value. These flags correspond to the attributes
4896 * {@link android.R.styleable#View_focusable android:focusable},
4897 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
4898 * {@link android.R.styleable#View_clickable android:clickable}, and
4899 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
4900 * flags to a state you had set previously, call one or more of the following methods:
4901 * {@link #setFocusable(boolean) setFocusable()},
4902 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
4903 * {@link #setClickable(boolean) setClickable()} or
4904 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08004905 *
Joe Malin10d96952013-05-29 17:49:09 -07004906 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004907 */
4908 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004909 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004910
Gilles Debunne5fae9962012-05-08 14:53:20 -07004911 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004912 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004913
Gilles Debunne2d373a12012-04-20 15:32:19 -07004914 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004915 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004916 setFocusable(selectable);
4917 setClickable(selectable);
4918 setLongClickable(selectable);
4919
Gilles Debunne60e21862012-01-30 15:04:14 -08004920 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004921
4922 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004923 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004924
4925 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004926 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004927 }
4928
4929 @Override
4930 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004931 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004932
Gilles Debunnefb817032011-01-13 13:52:49 -08004933 if (mSingleLine) {
4934 drawableState = super.onCreateDrawableState(extraSpace);
4935 } else {
4936 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004937 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4938 }
4939
Gilles Debunne60e21862012-01-30 15:04:14 -08004940 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004941 // Disable pressed state, which was introduced when TextView was made clickable.
4942 // Prevents text color change.
4943 // setClickable(false) would have a similar effect, but it also disables focus changes
4944 // and long press actions, which are both needed by text selection.
4945 final int length = drawableState.length;
4946 for (int i = 0; i < length; i++) {
4947 if (drawableState[i] == R.attr.state_pressed) {
4948 final int[] nonPressedState = new int[length - 1];
4949 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4950 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4951 return nonPressedState;
4952 }
4953 }
4954 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004955
Gilles Debunne86b9c782010-11-11 10:43:48 -08004956 return drawableState;
4957 }
4958
Gilles Debunne83051b82012-02-24 20:01:13 -08004959 private Path getUpdatedHighlightPath() {
4960 Path highlight = null;
4961 Paint highlightPaint = mHighlightPaint;
4962
4963 final int selStart = getSelectionStart();
4964 final int selEnd = getSelectionEnd();
4965 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4966 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004967 if (mEditor != null && mEditor.isCursorVisible() &&
4968 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004969 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004970 if (mHighlightPathBogus) {
4971 if (mHighlightPath == null) mHighlightPath = new Path();
4972 mHighlightPath.reset();
4973 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004974 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004975 mHighlightPathBogus = false;
4976 }
4977
4978 // XXX should pass to skin instead of drawing directly
4979 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004980 highlightPaint.setStyle(Paint.Style.STROKE);
4981 highlight = mHighlightPath;
4982 }
4983 } else {
4984 if (mHighlightPathBogus) {
4985 if (mHighlightPath == null) mHighlightPath = new Path();
4986 mHighlightPath.reset();
4987 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4988 mHighlightPathBogus = false;
4989 }
4990
4991 // XXX should pass to skin instead of drawing directly
4992 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004993 highlightPaint.setStyle(Paint.Style.FILL);
4994
4995 highlight = mHighlightPath;
4996 }
4997 }
4998 return highlight;
4999 }
5000
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005001 /**
5002 * @hide
5003 */
5004 public int getHorizontalOffsetForDrawables() {
5005 return 0;
5006 }
5007
Romain Guyc4d8eb62010-08-18 20:48:33 -07005008 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005009 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005010 restartMarqueeIfNeeded();
5011
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005012 // Draw the background for this view
5013 super.onDraw(canvas);
5014
5015 final int compoundPaddingLeft = getCompoundPaddingLeft();
5016 final int compoundPaddingTop = getCompoundPaddingTop();
5017 final int compoundPaddingRight = getCompoundPaddingRight();
5018 final int compoundPaddingBottom = getCompoundPaddingBottom();
5019 final int scrollX = mScrollX;
5020 final int scrollY = mScrollY;
5021 final int right = mRight;
5022 final int left = mLeft;
5023 final int bottom = mBottom;
5024 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005025 final boolean isLayoutRtl = isLayoutRtl();
5026 final int offset = getHorizontalOffsetForDrawables();
5027 final int leftOffset = isLayoutRtl ? 0 : offset;
5028 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005029
5030 final Drawables dr = mDrawables;
5031 if (dr != null) {
5032 /*
5033 * Compound, not extended, because the icon is not clipped
5034 * if the text height is smaller.
5035 */
5036
5037 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5038 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5039
Romain Guy3c77d392009-05-20 11:26:50 -07005040 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5041 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005042 if (dr.mDrawableLeft != null) {
5043 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005044 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005045 scrollY + compoundPaddingTop +
5046 (vspace - dr.mDrawableHeightLeft) / 2);
5047 dr.mDrawableLeft.draw(canvas);
5048 canvas.restore();
5049 }
5050
Romain Guy3c77d392009-05-20 11:26:50 -07005051 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5052 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005053 if (dr.mDrawableRight != null) {
5054 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005055 canvas.translate(scrollX + right - left - mPaddingRight
5056 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005057 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5058 dr.mDrawableRight.draw(canvas);
5059 canvas.restore();
5060 }
5061
Romain Guy3c77d392009-05-20 11:26:50 -07005062 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5063 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005064 if (dr.mDrawableTop != null) {
5065 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005066 canvas.translate(scrollX + compoundPaddingLeft +
5067 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005068 dr.mDrawableTop.draw(canvas);
5069 canvas.restore();
5070 }
5071
Romain Guy3c77d392009-05-20 11:26:50 -07005072 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5073 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005074 if (dr.mDrawableBottom != null) {
5075 canvas.save();
5076 canvas.translate(scrollX + compoundPaddingLeft +
5077 (hspace - dr.mDrawableWidthBottom) / 2,
5078 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5079 dr.mDrawableBottom.draw(canvas);
5080 canvas.restore();
5081 }
5082 }
5083
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005084 int color = mCurTextColor;
5085
5086 if (mLayout == null) {
5087 assumeLayout();
5088 }
5089
5090 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005091
5092 if (mHint != null && mText.length() == 0) {
5093 if (mHintTextColor != null) {
5094 color = mCurHintTextColor;
5095 }
5096
5097 layout = mHintLayout;
5098 }
5099
5100 mTextPaint.setColor(color);
5101 mTextPaint.drawableState = getDrawableState();
5102
5103 canvas.save();
5104 /* Would be faster if we didn't have to do this. Can we chop the
5105 (displayable) text so that we don't need to do this ever?
5106 */
5107
5108 int extendedPaddingTop = getExtendedPaddingTop();
5109 int extendedPaddingBottom = getExtendedPaddingBottom();
5110
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005111 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5112 final int maxScrollY = mLayout.getHeight() - vspace;
5113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005114 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005115 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005116 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005117 float clipBottom = bottom - top + scrollY -
5118 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005119
5120 if (mShadowRadius != 0) {
5121 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5122 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5123
5124 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5125 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5126 }
5127
5128 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5129
5130 int voffsetText = 0;
5131 int voffsetCursor = 0;
5132
5133 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005134 /* shortcircuit calling getVerticaOffset() */
5135 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5136 voffsetText = getVerticalOffset(false);
5137 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005138 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005139 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005140
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005141 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005142 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005143 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5144 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005145 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005146 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005147 final int width = mRight - mLeft;
5148 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5149 final float dx = mLayout.getLineRight(0) - (width - padding);
5150 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005151 }
5152
5153 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005154 final float dx = -mMarquee.getScroll();
5155 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005156 }
5157 }
5158
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005159 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005160
Gilles Debunne83051b82012-02-24 20:01:13 -08005161 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005162 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005163 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005164 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005165 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005167
Gilles Debunned88876a2012-03-16 17:34:04 -07005168 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005169 final int dx = (int) mMarquee.getGhostOffset();
5170 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005171 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005172 }
5173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005174 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005175 }
5176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005177 @Override
5178 public void getFocusedRect(Rect r) {
5179 if (mLayout == null) {
5180 super.getFocusedRect(r);
5181 return;
5182 }
5183
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005184 int selEnd = getSelectionEnd();
5185 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005186 super.getFocusedRect(r);
5187 return;
5188 }
5189
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005190 int selStart = getSelectionStart();
5191 if (selStart < 0 || selStart >= selEnd) {
5192 int line = mLayout.getLineForOffset(selEnd);
5193 r.top = mLayout.getLineTop(line);
5194 r.bottom = mLayout.getLineBottom(line);
5195 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5196 r.right = r.left + 4;
5197 } else {
5198 int lineStart = mLayout.getLineForOffset(selStart);
5199 int lineEnd = mLayout.getLineForOffset(selEnd);
5200 r.top = mLayout.getLineTop(lineStart);
5201 r.bottom = mLayout.getLineBottom(lineEnd);
5202 if (lineStart == lineEnd) {
5203 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5204 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5205 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005206 // Selection extends across multiple lines -- make the focused
5207 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005208 if (mHighlightPathBogus) {
5209 if (mHighlightPath == null) mHighlightPath = new Path();
5210 mHighlightPath.reset();
5211 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5212 mHighlightPathBogus = false;
5213 }
5214 synchronized (TEMP_RECTF) {
5215 mHighlightPath.computeBounds(TEMP_RECTF, true);
5216 r.left = (int)TEMP_RECTF.left-1;
5217 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005218 }
5219 }
5220 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005221
5222 // Adjust for padding and gravity.
5223 int paddingLeft = getCompoundPaddingLeft();
5224 int paddingTop = getExtendedPaddingTop();
5225 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5226 paddingTop += getVerticalOffset(false);
5227 }
5228 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005229 int paddingBottom = getExtendedPaddingBottom();
5230 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005231 }
5232
5233 /**
5234 * Return the number of lines of text, or 0 if the internal Layout has not
5235 * been built.
5236 */
5237 public int getLineCount() {
5238 return mLayout != null ? mLayout.getLineCount() : 0;
5239 }
5240
5241 /**
5242 * Return the baseline for the specified line (0...getLineCount() - 1)
5243 * If bounds is not null, return the top, left, right, bottom extents
5244 * of the specified line in it. If the internal Layout has not been built,
5245 * return 0 and set bounds to (0, 0, 0, 0)
5246 * @param line which line to examine (0..getLineCount() - 1)
5247 * @param bounds Optional. If not null, it returns the extent of the line
5248 * @return the Y-coordinate of the baseline
5249 */
5250 public int getLineBounds(int line, Rect bounds) {
5251 if (mLayout == null) {
5252 if (bounds != null) {
5253 bounds.set(0, 0, 0, 0);
5254 }
5255 return 0;
5256 }
5257 else {
5258 int baseline = mLayout.getLineBounds(line, bounds);
5259
5260 int voffset = getExtendedPaddingTop();
5261 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5262 voffset += getVerticalOffset(true);
5263 }
5264 if (bounds != null) {
5265 bounds.offset(getCompoundPaddingLeft(), voffset);
5266 }
5267 return baseline + voffset;
5268 }
5269 }
5270
5271 @Override
5272 public int getBaseline() {
5273 if (mLayout == null) {
5274 return super.getBaseline();
5275 }
5276
5277 int voffset = 0;
5278 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5279 voffset = getVerticalOffset(true);
5280 }
5281
Philip Milne7b757812012-09-19 18:13:44 -07005282 if (isLayoutModeOptical(mParent)) {
5283 voffset -= getOpticalInsets().top;
5284 }
5285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005286 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5287 }
5288
Romain Guyf2fc4602011-07-19 15:20:03 -07005289 /**
5290 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005291 */
5292 @Override
5293 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005294 if (mLayout == null) return 0;
5295
Romain Guyf2fc4602011-07-19 15:20:03 -07005296 int voffset = 0;
5297 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5298 voffset = getVerticalOffset(true);
5299 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005300
Romain Guyf2fc4602011-07-19 15:20:03 -07005301 if (offsetRequired) voffset += getTopPaddingOffset();
5302
5303 return getExtendedPaddingTop() + voffset;
5304 }
5305
5306 /**
5307 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005308 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005309 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005310 protected int getFadeHeight(boolean offsetRequired) {
5311 return mLayout != null ? mLayout.getHeight() : 0;
5312 }
5313
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005314 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005315 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5316 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005317 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005318
Gilles Debunne28294cc2011-08-24 12:02:05 -07005319 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005320 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5321 KeyEvent.DispatcherState state = getKeyDispatcherState();
5322 if (state != null) {
5323 state.startTracking(event, this);
5324 }
5325 return true;
5326 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5327 KeyEvent.DispatcherState state = getKeyDispatcherState();
5328 if (state != null) {
5329 state.handleUpEvent(event);
5330 }
5331 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005332 stopSelectionActionMode();
5333 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005334 }
5335 }
5336 }
5337 }
5338 return super.onKeyPreIme(keyCode, event);
5339 }
5340
5341 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005342 public boolean onKeyDown(int keyCode, KeyEvent event) {
5343 int which = doKeyDown(keyCode, event, null);
5344 if (which == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005345 return super.onKeyDown(keyCode, event);
5346 }
5347
5348 return true;
5349 }
5350
5351 @Override
5352 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005353 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005354
5355 int which = doKeyDown(keyCode, down, event);
5356 if (which == 0) {
5357 // Go through default dispatching.
5358 return super.onKeyMultiple(keyCode, repeatCount, event);
5359 }
5360 if (which == -1) {
5361 // Consumed the whole thing.
5362 return true;
5363 }
5364
5365 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005366
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005367 // We are going to dispatch the remaining events to either the input
5368 // or movement method. To do this, we will just send a repeated stream
5369 // of down and up events until we have done the complete repeatCount.
5370 // It would be nice if those interfaces had an onKeyMultiple() method,
5371 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005372 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005373 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005374 // mEditor and mEditor.mInput are not null from doKeyDown
5375 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005376 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005377 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5378 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005379 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005380 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005381
5382 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005383 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005384 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5385 while (--repeatCount > 0) {
5386 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5387 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5388 }
5389 }
5390
5391 return true;
5392 }
5393
5394 /**
5395 * Returns true if pressing ENTER in this field advances focus instead
5396 * of inserting the character. This is true mostly in single-line fields,
5397 * but also in mail addresses and subjects which will display on multiple
5398 * lines but where it doesn't make sense to insert newlines.
5399 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005400 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005401 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005402 return false;
5403 }
5404
5405 if (mSingleLine) {
5406 return true;
5407 }
5408
Gilles Debunne2d373a12012-04-20 15:32:19 -07005409 if (mEditor != null &&
5410 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5411 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005412 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5413 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005414 return true;
5415 }
5416 }
5417
5418 return false;
5419 }
5420
Jeff Brown4e6319b2010-12-13 10:36:51 -08005421 /**
5422 * Returns true if pressing TAB in this field advances focus instead
5423 * of inserting the character. Insert tabs only in multi-line editors.
5424 */
5425 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005426 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5427 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5428 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5429 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5430 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5431 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005432 }
5433 }
5434 return true;
5435 }
5436
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005437 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5438 if (!isEnabled()) {
5439 return 0;
5440 }
5441
Michael Wright3a7e4832013-02-11 15:55:50 -08005442 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5443 // While this shouldn't be necessary because any time we're preventing default movement we
5444 // should be restricting the focus to remain within this view, thus we'll also receive
5445 // the key up event, occasionally key up events will get dropped and we don't want to
5446 // prevent the user from traversing out of this on the next key down.
5447 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5448 mPreventDefaultMovement = false;
5449 }
5450
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005451 switch (keyCode) {
5452 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005453 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005454 // When mInputContentType is set, we know that we are
5455 // running in a "modern" cupcake environment, so don't need
5456 // to worry about the application trying to capture
5457 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005458 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005459 // If there is an action listener, given them a
5460 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005461 if (mEditor.mInputContentType.onEditorActionListener != null &&
5462 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005463 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005464 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005465 // We are consuming the enter key for them.
5466 return -1;
5467 }
5468 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005469
The Android Open Source Project10592532009-03-18 17:39:46 -07005470 // If our editor should move focus when enter is pressed, or
5471 // this is a generated event from an IME action button, then
5472 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005473 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005474 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005475 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005476 return 0;
5477 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005478 return -1;
5479 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005480 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005481 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005482
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005483 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005484 if (event.hasNoModifiers()) {
5485 if (shouldAdvanceFocusOnEnter()) {
5486 return 0;
5487 }
5488 }
5489 break;
5490
5491 case KeyEvent.KEYCODE_TAB:
5492 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5493 if (shouldAdvanceFocusOnTab()) {
5494 return 0;
5495 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005496 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005497 break;
5498
5499 // Has to be done on key down (and not on key up) to correctly be intercepted.
5500 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005501 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005502 stopSelectionActionMode();
5503 return -1;
5504 }
5505 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005506 }
5507
Gilles Debunne2d373a12012-04-20 15:32:19 -07005508 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005509 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005510
5511 boolean doDown = true;
5512 if (otherEvent != null) {
5513 try {
5514 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005515 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5516 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005517 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005518 doDown = false;
5519 if (handled) {
5520 return -1;
5521 }
5522 } catch (AbstractMethodError e) {
5523 // onKeyOther was added after 1.0, so if it isn't
5524 // implemented we need to try to dispatch as a regular down.
5525 } finally {
5526 endBatchEdit();
5527 }
5528 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005529
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005530 if (doDown) {
5531 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005532 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5533 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005534 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005535 hideErrorIfUnchanged();
5536 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005537 }
5538 }
5539
5540 // bug 650865: sometimes we get a key event before a layout.
5541 // don't try to move around if we don't know the layout.
5542
5543 if (mMovement != null && mLayout != null) {
5544 boolean doDown = true;
5545 if (otherEvent != null) {
5546 try {
5547 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5548 otherEvent);
5549 doDown = false;
5550 if (handled) {
5551 return -1;
5552 }
5553 } catch (AbstractMethodError e) {
5554 // onKeyOther was added after 1.0, so if it isn't
5555 // implemented we need to try to dispatch as a regular down.
5556 }
5557 }
5558 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08005559 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5560 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5561 mPreventDefaultMovement = true;
5562 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005563 return 2;
Michael Wright3a7e4832013-02-11 15:55:50 -08005564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005565 }
5566 }
5567
Michael Wright3a7e4832013-02-11 15:55:50 -08005568 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005569 }
5570
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005571 /**
5572 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5573 * can be recorded.
5574 * @hide
5575 */
5576 public void resetErrorChangedFlag() {
5577 /*
5578 * Keep track of what the error was before doing the input
5579 * so that if an input filter changed the error, we leave
5580 * that error showing. Otherwise, we take down whatever
5581 * error was showing when the user types something.
5582 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005583 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005584 }
5585
5586 /**
5587 * @hide
5588 */
5589 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005590 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005591 setError(null, null);
5592 }
5593 }
5594
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005595 @Override
5596 public boolean onKeyUp(int keyCode, KeyEvent event) {
5597 if (!isEnabled()) {
5598 return super.onKeyUp(keyCode, event);
5599 }
5600
Michael Wright3a7e4832013-02-11 15:55:50 -08005601 if (!KeyEvent.isModifierKey(keyCode)) {
5602 mPreventDefaultMovement = false;
5603 }
5604
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005605 switch (keyCode) {
5606 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005607 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005608 /*
5609 * If there is a click listener, just call through to
5610 * super, which will invoke it.
5611 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005612 * If there isn't a click listener, try to show the soft
5613 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005614 * call performClick(), but that won't do anything in
5615 * this case.)
5616 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005617 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005618 if (mMovement != null && mText instanceof Editable
5619 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005620 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005621 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005622 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005623 imm.showSoftInput(this, 0);
5624 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005625 }
5626 }
5627 }
5628 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005629
Jeff Brown4e6319b2010-12-13 10:36:51 -08005630 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005631 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005632 if (mEditor != null && mEditor.mInputContentType != null
5633 && mEditor.mInputContentType.onEditorActionListener != null
5634 && mEditor.mInputContentType.enterDown) {
5635 mEditor.mInputContentType.enterDown = false;
5636 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005637 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005638 return true;
5639 }
5640 }
5641
Jeff Brown4e6319b2010-12-13 10:36:51 -08005642 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5643 || shouldAdvanceFocusOnEnter()) {
5644 /*
5645 * If there is a click listener, just call through to
5646 * super, which will invoke it.
5647 *
5648 * If there isn't a click listener, try to advance focus,
5649 * but still call through to super, which will reset the
5650 * pressed state and longpress state. (It will also
5651 * call performClick(), but that won't do anything in
5652 * this case.)
5653 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005654 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005655 View v = focusSearch(FOCUS_DOWN);
5656
5657 if (v != null) {
5658 if (!v.requestFocus(FOCUS_DOWN)) {
5659 throw new IllegalStateException(
5660 "focus search returned a view " +
5661 "that wasn't able to take focus!");
5662 }
5663
5664 /*
5665 * Return true because we handled the key; super
5666 * will return false because there was no click
5667 * listener.
5668 */
5669 super.onKeyUp(keyCode, event);
5670 return true;
5671 } else if ((event.getFlags()
5672 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5673 // No target for next focus, but make sure the IME
5674 // if this came from it.
5675 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005676 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005677 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5678 }
5679 }
5680 }
5681 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005682 return super.onKeyUp(keyCode, event);
5683 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005684 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005685 }
5686
Gilles Debunne2d373a12012-04-20 15:32:19 -07005687 if (mEditor != null && mEditor.mKeyListener != null)
5688 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005689 return true;
5690
5691 if (mMovement != null && mLayout != null)
5692 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5693 return true;
5694
5695 return super.onKeyUp(keyCode, event);
5696 }
5697
Gilles Debunnec1714022012-01-17 13:59:23 -08005698 @Override
5699 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005700 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005701 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005702
Gilles Debunnec1714022012-01-17 13:59:23 -08005703 @Override
5704 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005705 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005706 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005707 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005708 if (mEditor.mInputContentType != null) {
5709 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5710 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5711 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5712 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5713 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005714 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005715 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005716 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005717 if (focusSearch(FOCUS_DOWN) != null) {
5718 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5719 }
5720 if (focusSearch(FOCUS_UP) != null) {
5721 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5722 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005723 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5724 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005725 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005726 // An action has not been set, but the enter key will move to
5727 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005728 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005729 } else {
5730 // An action has not been set, and there is no focus to move
5731 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005732 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005733 }
5734 if (!shouldAdvanceFocusOnEnter()) {
5735 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005736 }
5737 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005738 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005739 // Multi-line text editors should always show an enter key.
5740 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5741 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005742 outAttrs.hintText = mHint;
5743 if (mText instanceof Editable) {
5744 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005745 outAttrs.initialSelStart = getSelectionStart();
5746 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005747 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005748 return ic;
5749 }
5750 }
5751 return null;
5752 }
5753
5754 /**
5755 * If this TextView contains editable content, extract a portion of it
5756 * based on the information in <var>request</var> in to <var>outText</var>.
5757 * @return Returns true if the text was successfully extracted, else false.
5758 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005759 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005760 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005761 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005762 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005763
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005764 /**
5765 * This is used to remove all style-impacting spans from text before new
5766 * extracted text is being replaced into it, so that we don't have any
5767 * lingering spans applied during the replace.
5768 */
5769 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5770 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5771 int i = spans.length;
5772 while (i > 0) {
5773 i--;
5774 spannable.removeSpan(spans[i]);
5775 }
5776 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005778 /**
5779 * Apply to this text view the given extracted text, as previously
5780 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5781 */
5782 public void setExtractedText(ExtractedText text) {
5783 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005784 if (text.text != null) {
5785 if (content == null) {
5786 setText(text.text, TextView.BufferType.EDITABLE);
5787 } else if (text.partialStartOffset < 0) {
5788 removeParcelableSpans(content, 0, content.length());
5789 content.replace(0, content.length(), text.text);
5790 } else {
5791 final int N = content.length();
5792 int start = text.partialStartOffset;
5793 if (start > N) start = N;
5794 int end = text.partialEndOffset;
5795 if (end > N) end = N;
5796 removeParcelableSpans(content, start, end);
5797 content.replace(start, end, text.text);
5798 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005799 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005801 // Now set the selection position... make sure it is in range, to
5802 // avoid crashes. If this is a partial update, it is possible that
5803 // the underlying text may have changed, causing us problems here.
5804 // Also we just don't want to trust clients to do the right thing.
5805 Spannable sp = (Spannable)getText();
5806 final int N = sp.length();
5807 int start = text.selectionStart;
5808 if (start < 0) start = 0;
5809 else if (start > N) start = N;
5810 int end = text.selectionEnd;
5811 if (end < 0) end = 0;
5812 else if (end > N) end = N;
5813 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005814
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005815 // Finally, update the selection mode.
5816 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5817 MetaKeyKeyListener.startSelecting(this, sp);
5818 } else {
5819 MetaKeyKeyListener.stopSelecting(this, sp);
5820 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005821 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005822
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005823 /**
5824 * @hide
5825 */
5826 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005827 if (mEditor.mInputMethodState != null) {
5828 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005829 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005830 // This would stop a possible selection mode, but no such mode is started in case
5831 // extracted mode will start. Some text is selected though, and will trigger an action mode
5832 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005833 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005834 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005835
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005836 /**
5837 * Called by the framework in response to a text completion from
5838 * the current input method, provided by it calling
5839 * {@link InputConnection#commitCompletion
5840 * InputConnection.commitCompletion()}. The default implementation does
5841 * nothing; text views that are supporting auto-completion should override
5842 * this to do their desired behavior.
5843 *
5844 * @param text The auto complete text the user has selected.
5845 */
5846 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005847 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005848 }
5849
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005850 /**
5851 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5852 * a dictionnary) from the current input method, provided by it calling
5853 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5854 * implementation flashes the background of the corrected word to provide feedback to the user.
5855 *
5856 * @param info The auto correct info about the text that was corrected.
5857 */
5858 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005859 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005860 }
5861
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005862 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005863 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005864 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005865
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005866 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005867 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005868 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005869
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005870 /**
5871 * Called by the framework in response to a request to begin a batch
5872 * of edit operations through a call to link {@link #beginBatchEdit()}.
5873 */
5874 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005875 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005876 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005877
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005878 /**
5879 * Called by the framework in response to a request to end a batch
5880 * of edit operations through a call to link {@link #endBatchEdit}.
5881 */
5882 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005883 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005884 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005885
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005886 /**
5887 * Called by the framework in response to a private command from the
5888 * current method, provided by it calling
5889 * {@link InputConnection#performPrivateCommand
5890 * InputConnection.performPrivateCommand()}.
5891 *
5892 * @param action The action name of the command.
5893 * @param data Any additional data for the command. This may be null.
5894 * @return Return true if you handled the command, else false.
5895 */
5896 public boolean onPrivateIMECommand(String action, Bundle data) {
5897 return false;
5898 }
5899
5900 private void nullLayouts() {
5901 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5902 mSavedLayout = (BoringLayout) mLayout;
5903 }
5904 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5905 mSavedHintLayout = (BoringLayout) mHintLayout;
5906 }
5907
Adam Powell282e3772011-08-30 16:51:11 -07005908 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005909
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005910 mBoring = mHintBoring = null;
5911
Gilles Debunne77f18b02010-10-22 14:28:25 -07005912 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005913 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005914 }
5915
5916 /**
5917 * Make a new Layout based on the already-measured size of the view,
5918 * on the assumption that it was measured correctly at some point.
5919 */
5920 private void assumeLayout() {
5921 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5922
5923 if (width < 1) {
5924 width = 0;
5925 }
5926
5927 int physicalWidth = width;
5928
5929 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005930 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005931 }
5932
5933 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5934 physicalWidth, false);
5935 }
5936
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005937 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005938 Layout.Alignment alignment;
5939 switch (getTextAlignment()) {
5940 case TEXT_ALIGNMENT_GRAVITY:
5941 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5942 case Gravity.START:
5943 alignment = Layout.Alignment.ALIGN_NORMAL;
5944 break;
5945 case Gravity.END:
5946 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5947 break;
5948 case Gravity.LEFT:
5949 alignment = Layout.Alignment.ALIGN_LEFT;
5950 break;
5951 case Gravity.RIGHT:
5952 alignment = Layout.Alignment.ALIGN_RIGHT;
5953 break;
5954 case Gravity.CENTER_HORIZONTAL:
5955 alignment = Layout.Alignment.ALIGN_CENTER;
5956 break;
5957 default:
5958 alignment = Layout.Alignment.ALIGN_NORMAL;
5959 break;
5960 }
5961 break;
5962 case TEXT_ALIGNMENT_TEXT_START:
5963 alignment = Layout.Alignment.ALIGN_NORMAL;
5964 break;
5965 case TEXT_ALIGNMENT_TEXT_END:
5966 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5967 break;
5968 case TEXT_ALIGNMENT_CENTER:
5969 alignment = Layout.Alignment.ALIGN_CENTER;
5970 break;
5971 case TEXT_ALIGNMENT_VIEW_START:
5972 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5973 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5974 break;
5975 case TEXT_ALIGNMENT_VIEW_END:
5976 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5977 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5978 break;
5979 case TEXT_ALIGNMENT_INHERIT:
5980 // This should never happen as we have already resolved the text alignment
5981 // but better safe than sorry so we just fall through
5982 default:
5983 alignment = Layout.Alignment.ALIGN_NORMAL;
5984 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005985 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005986 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005987 }
5988
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005989 /**
5990 * The width passed in is now the desired layout width,
5991 * not the full view width with padding.
5992 * {@hide}
5993 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07005994 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005995 BoringLayout.Metrics boring,
5996 BoringLayout.Metrics hintBoring,
5997 int ellipsisWidth, boolean bringIntoView) {
5998 stopMarquee();
5999
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006000 // Update "old" cached values
6001 mOldMaximum = mMaximum;
6002 mOldMaxMode = mMaxMode;
6003
Gilles Debunne83051b82012-02-24 20:01:13 -08006004 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006005
Gilles Debunne287d6c62011-10-05 18:22:11 -07006006 if (wantWidth < 0) {
6007 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006008 }
6009 if (hintWidth < 0) {
6010 hintWidth = 0;
6011 }
6012
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006013 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006014 final boolean testDirChange = mSingleLine && mLayout != null &&
6015 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6016 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6017 int oldDir = 0;
6018 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006019 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006020 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6021 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6022 TruncateAt effectiveEllipsize = mEllipsize;
6023 if (mEllipsize == TruncateAt.MARQUEE &&
6024 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006025 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006026 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006027
Doug Feltcb3791202011-07-07 11:57:48 -07006028 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006029 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006030 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006031
Gilles Debunne287d6c62011-10-05 18:22:11 -07006032 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006033 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6034 if (switchEllipsize) {
6035 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6036 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006037 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006038 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006039 }
6040
Romain Guy4dc4f732009-06-19 15:16:40 -07006041 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006042 mHintLayout = null;
6043
6044 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006045 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006046
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006047 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006048 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006049 mHintBoring);
6050 if (hintBoring != null) {
6051 mHintBoring = hintBoring;
6052 }
6053 }
6054
6055 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006056 if (hintBoring.width <= hintWidth &&
6057 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006058 if (mSavedHintLayout != null) {
6059 mHintLayout = mSavedHintLayout.
6060 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006061 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6062 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006063 } else {
6064 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006065 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6066 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006067 }
6068
6069 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006070 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6071 if (mSavedHintLayout != null) {
6072 mHintLayout = mSavedHintLayout.
6073 replaceOrMake(mHint, mTextPaint,
6074 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6075 hintBoring, mIncludePad, mEllipsize,
6076 ellipsisWidth);
6077 } else {
6078 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6079 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6080 hintBoring, mIncludePad, mEllipsize,
6081 ellipsisWidth);
6082 }
6083 } else if (shouldEllipsize) {
6084 mHintLayout = new StaticLayout(mHint,
6085 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006086 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006087 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006088 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006089 } else {
6090 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006091 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006092 mIncludePad);
6093 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006094 } else if (shouldEllipsize) {
6095 mHintLayout = new StaticLayout(mHint,
6096 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006097 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006098 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006099 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006100 } else {
6101 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006102 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006103 mIncludePad);
6104 }
6105 }
6106
Raph Levienf5cf6c92013-04-12 11:31:31 -07006107 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006108 registerForPreDraw();
6109 }
6110
6111 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006112 if (!compressText(ellipsisWidth)) {
6113 final int height = mLayoutParams.height;
6114 // If the size of the view does not depend on the size of the text, try to
6115 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08006116 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006117 startMarquee();
6118 } else {
6119 // Defer the start of the marquee until we know our width (see setFrame())
6120 mRestartMarquee = true;
6121 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006122 }
6123 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006124
6125 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006126 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006127 }
6128
Gilles Debunne287d6c62011-10-05 18:22:11 -07006129 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006130 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6131 boolean useSaved) {
6132 Layout result = null;
6133 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006134 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006135 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08006136 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07006137 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006138 } else {
6139 if (boring == UNKNOWN_BORING) {
6140 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6141 if (boring != null) {
6142 mBoring = boring;
6143 }
6144 }
6145
6146 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006147 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006148 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6149 if (useSaved && mSavedLayout != null) {
6150 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006151 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006152 boring, mIncludePad);
6153 } else {
6154 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006155 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006156 boring, mIncludePad);
6157 }
6158
6159 if (useSaved) {
6160 mSavedLayout = (BoringLayout) result;
6161 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006162 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006163 if (useSaved && mSavedLayout != null) {
6164 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006165 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006166 boring, mIncludePad, effectiveEllipsize,
6167 ellipsisWidth);
6168 } else {
6169 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006170 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006171 boring, mIncludePad, effectiveEllipsize,
6172 ellipsisWidth);
6173 }
6174 } else if (shouldEllipsize) {
6175 result = new StaticLayout(mTransformed,
6176 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006177 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006178 mSpacingAdd, mIncludePad, effectiveEllipsize,
6179 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6180 } else {
6181 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006182 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006183 mIncludePad);
6184 }
6185 } else if (shouldEllipsize) {
6186 result = new StaticLayout(mTransformed,
6187 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006188 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006189 mSpacingAdd, mIncludePad, effectiveEllipsize,
6190 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6191 } else {
6192 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006193 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006194 mIncludePad);
6195 }
6196 }
6197 return result;
6198 }
6199
Romain Guy939151f2009-04-08 14:22:40 -07006200 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006201 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006202
Romain Guy3373ed62009-05-04 14:13:32 -07006203 // Only compress the text if it hasn't been compressed by the previous pass
6204 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6205 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006206 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006207 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006208 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6209 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6210 post(new Runnable() {
6211 public void run() {
6212 requestLayout();
6213 }
6214 });
6215 return true;
6216 }
6217 }
6218
6219 return false;
6220 }
6221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006222 private static int desired(Layout layout) {
6223 int n = layout.getLineCount();
6224 CharSequence text = layout.getText();
6225 float max = 0;
6226
6227 // if any line was wrapped, we can't use it.
6228 // but it's ok for the last line not to have a newline
6229
6230 for (int i = 0; i < n - 1; i++) {
6231 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6232 return -1;
6233 }
6234
6235 for (int i = 0; i < n; i++) {
6236 max = Math.max(max, layout.getLineWidth(i));
6237 }
6238
6239 return (int) FloatMath.ceil(max);
6240 }
6241
6242 /**
6243 * Set whether the TextView includes extra top and bottom padding to make
6244 * room for accents that go above the normal ascent and descent.
6245 * The default is true.
6246 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006247 * @see #getIncludeFontPadding()
6248 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006249 * @attr ref android.R.styleable#TextView_includeFontPadding
6250 */
6251 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006252 if (mIncludePad != includepad) {
6253 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006254
Gilles Debunne22378292011-08-12 10:38:52 -07006255 if (mLayout != null) {
6256 nullLayouts();
6257 requestLayout();
6258 invalidate();
6259 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006260 }
6261 }
6262
Gilles Debunnef03acef2012-04-30 19:26:19 -07006263 /**
6264 * Gets whether the TextView includes extra top and bottom padding to make
6265 * room for accents that go above the normal ascent and descent.
6266 *
6267 * @see #setIncludeFontPadding(boolean)
6268 *
6269 * @attr ref android.R.styleable#TextView_includeFontPadding
6270 */
6271 public boolean getIncludeFontPadding() {
6272 return mIncludePad;
6273 }
6274
Romain Guy4dc4f732009-06-19 15:16:40 -07006275 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006276
6277 @Override
6278 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6279 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6280 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6281 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6282 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6283
6284 int width;
6285 int height;
6286
6287 BoringLayout.Metrics boring = UNKNOWN_BORING;
6288 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6289
Doug Feltcb3791202011-07-07 11:57:48 -07006290 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07006291 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006292 }
6293
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006294 int des = -1;
6295 boolean fromexisting = false;
6296
6297 if (widthMode == MeasureSpec.EXACTLY) {
6298 // Parent has told us how big to be. So be it.
6299 width = widthSize;
6300 } else {
6301 if (mLayout != null && mEllipsize == null) {
6302 des = desired(mLayout);
6303 }
6304
6305 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006306 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006307 if (boring != null) {
6308 mBoring = boring;
6309 }
6310 } else {
6311 fromexisting = true;
6312 }
6313
6314 if (boring == null || boring == UNKNOWN_BORING) {
6315 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006316 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006317 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006318 width = des;
6319 } else {
6320 width = boring.width;
6321 }
6322
6323 final Drawables dr = mDrawables;
6324 if (dr != null) {
6325 width = Math.max(width, dr.mDrawableWidthTop);
6326 width = Math.max(width, dr.mDrawableWidthBottom);
6327 }
6328
6329 if (mHint != null) {
6330 int hintDes = -1;
6331 int hintWidth;
6332
Romain Guy4dc4f732009-06-19 15:16:40 -07006333 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006334 hintDes = desired(mHintLayout);
6335 }
6336
6337 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006338 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006339 if (hintBoring != null) {
6340 mHintBoring = hintBoring;
6341 }
6342 }
6343
6344 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6345 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006346 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006347 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006348 hintWidth = hintDes;
6349 } else {
6350 hintWidth = hintBoring.width;
6351 }
6352
6353 if (hintWidth > width) {
6354 width = hintWidth;
6355 }
6356 }
6357
6358 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6359
6360 if (mMaxWidthMode == EMS) {
6361 width = Math.min(width, mMaxWidth * getLineHeight());
6362 } else {
6363 width = Math.min(width, mMaxWidth);
6364 }
6365
6366 if (mMinWidthMode == EMS) {
6367 width = Math.max(width, mMinWidth * getLineHeight());
6368 } else {
6369 width = Math.max(width, mMinWidth);
6370 }
6371
6372 // Check against our minimum width
6373 width = Math.max(width, getSuggestedMinimumWidth());
6374
6375 if (widthMode == MeasureSpec.AT_MOST) {
6376 width = Math.min(widthSize, width);
6377 }
6378 }
6379
6380 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6381 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006382
Jeff Brown033a0012011-11-11 15:30:16 -08006383 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006384
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006385 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006386 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006387
6388 if (mLayout == null) {
6389 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006390 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006391 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006392 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6393 (hintWidth != hintWant) ||
6394 (mLayout.getEllipsizedWidth() !=
6395 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6396
6397 final boolean widthChanged = (mHint == null) &&
6398 (mEllipsize == null) &&
6399 (want > mLayout.getWidth()) &&
6400 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6401
6402 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6403
6404 if (layoutChanged || maximumChanged) {
6405 if (!maximumChanged && widthChanged) {
6406 mLayout.increaseWidthTo(want);
6407 } else {
6408 makeNewLayout(want, hintWant, boring, hintBoring,
6409 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6410 }
6411 } else {
6412 // Nothing has changed
6413 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006414 }
6415
6416 if (heightMode == MeasureSpec.EXACTLY) {
6417 // Parent has told us how big to be. So be it.
6418 height = heightSize;
6419 mDesiredHeightAtMeasure = -1;
6420 } else {
6421 int desired = getDesiredHeight();
6422
6423 height = desired;
6424 mDesiredHeightAtMeasure = desired;
6425
6426 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006427 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006428 }
6429 }
6430
Romain Guy4dc4f732009-06-19 15:16:40 -07006431 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006432 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006433 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006434 }
6435
6436 /*
6437 * We didn't let makeNewLayout() register to bring the cursor into view,
6438 * so do it here if there is any possibility that it is needed.
6439 */
6440 if (mMovement != null ||
6441 mLayout.getWidth() > unpaddedWidth ||
6442 mLayout.getHeight() > unpaddedHeight) {
6443 registerForPreDraw();
6444 } else {
6445 scrollTo(0, 0);
6446 }
6447
6448 setMeasuredDimension(width, height);
6449 }
6450
6451 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006452 return Math.max(
6453 getDesiredHeight(mLayout, true),
6454 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006455 }
6456
6457 private int getDesiredHeight(Layout layout, boolean cap) {
6458 if (layout == null) {
6459 return 0;
6460 }
6461
6462 int linecount = layout.getLineCount();
6463 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6464 int desired = layout.getLineTop(linecount);
6465
6466 final Drawables dr = mDrawables;
6467 if (dr != null) {
6468 desired = Math.max(desired, dr.mDrawableHeightLeft);
6469 desired = Math.max(desired, dr.mDrawableHeightRight);
6470 }
6471
6472 desired += pad;
6473
6474 if (mMaxMode == LINES) {
6475 /*
6476 * Don't cap the hint to a certain number of lines.
6477 * (Do cap it, though, if we have a maximum pixel height.)
6478 */
6479 if (cap) {
6480 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006481 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006482
6483 if (dr != null) {
6484 desired = Math.max(desired, dr.mDrawableHeightLeft);
6485 desired = Math.max(desired, dr.mDrawableHeightRight);
6486 }
6487
6488 desired += pad;
6489 linecount = mMaximum;
6490 }
6491 }
6492 } else {
6493 desired = Math.min(desired, mMaximum);
6494 }
6495
6496 if (mMinMode == LINES) {
6497 if (linecount < mMinimum) {
6498 desired += getLineHeight() * (mMinimum - linecount);
6499 }
6500 } else {
6501 desired = Math.max(desired, mMinimum);
6502 }
6503
6504 // Check against our minimum height
6505 desired = Math.max(desired, getSuggestedMinimumHeight());
6506
6507 return desired;
6508 }
6509
6510 /**
6511 * Check whether a change to the existing text layout requires a
6512 * new view layout.
6513 */
6514 private void checkForResize() {
6515 boolean sizeChanged = false;
6516
6517 if (mLayout != null) {
6518 // Check if our width changed
6519 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6520 sizeChanged = true;
6521 invalidate();
6522 }
6523
6524 // Check if our height changed
6525 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6526 int desiredHeight = getDesiredHeight();
6527
6528 if (desiredHeight != this.getHeight()) {
6529 sizeChanged = true;
6530 }
Romain Guy980a9382010-01-08 15:06:28 -08006531 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006532 if (mDesiredHeightAtMeasure >= 0) {
6533 int desiredHeight = getDesiredHeight();
6534
6535 if (desiredHeight != mDesiredHeightAtMeasure) {
6536 sizeChanged = true;
6537 }
6538 }
6539 }
6540 }
6541
6542 if (sizeChanged) {
6543 requestLayout();
6544 // caller will have already invalidated
6545 }
6546 }
6547
6548 /**
6549 * Check whether entirely new text requires a new view layout
6550 * or merely a new text layout.
6551 */
6552 private void checkForRelayout() {
6553 // If we have a fixed width, we can just swap in a new text layout
6554 // if the text height stays the same or if the view height is fixed.
6555
6556 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6557 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6558 (mHint == null || mHintLayout != null) &&
6559 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6560 // Static width, so try making a new text layout.
6561
6562 int oldht = mLayout.getHeight();
6563 int want = mLayout.getWidth();
6564 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6565
6566 /*
6567 * No need to bring the text into view, since the size is not
6568 * changing (unless we do the requestLayout(), in which case it
6569 * will happen at measure).
6570 */
6571 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006572 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6573 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006574
Romain Guye1e0dc82009-11-03 17:21:04 -08006575 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6576 // In a fixed-height view, so use our new text layout.
6577 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006578 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006579 invalidate();
6580 return;
6581 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006582
Romain Guye1e0dc82009-11-03 17:21:04 -08006583 // Dynamic height, but height has stayed the same,
6584 // so use our new text layout.
6585 if (mLayout.getHeight() == oldht &&
6586 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6587 invalidate();
6588 return;
6589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006590 }
6591
6592 // We lose: the height has changed and we have a dynamic height.
6593 // Request a new view layout using our new text layout.
6594 requestLayout();
6595 invalidate();
6596 } else {
6597 // Dynamic width, so we have no choice but to request a new
6598 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006599 nullLayouts();
6600 requestLayout();
6601 invalidate();
6602 }
6603 }
6604
Gilles Debunne954325e2012-01-25 11:57:06 -08006605 @Override
6606 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6607 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006608 if (mDeferScroll >= 0) {
6609 int curs = mDeferScroll;
6610 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006611 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006612 }
Gilles Debunne954325e2012-01-25 11:57:06 -08006613 }
6614
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006615 private boolean isShowingHint() {
6616 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6617 }
6618
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006619 /**
6620 * Returns true if anything changed.
6621 */
6622 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006623 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006624 int line = 0;
6625 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006626 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006627 }
6628
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006629 Layout.Alignment a = layout.getParagraphAlignment(line);
6630 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006631 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6632 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006633 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006634
6635 int scrollx, scrolly;
6636
Doug Felt25b9f422011-07-11 13:48:37 -07006637 // Convert to left, center, or right alignment.
6638 if (a == Layout.Alignment.ALIGN_NORMAL) {
6639 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6640 Layout.Alignment.ALIGN_RIGHT;
6641 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6642 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6643 Layout.Alignment.ALIGN_LEFT;
6644 }
6645
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006646 if (a == Layout.Alignment.ALIGN_CENTER) {
6647 /*
6648 * Keep centered if possible, or, if it is too wide to fit,
6649 * keep leading edge in view.
6650 */
6651
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006652 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6653 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006654
6655 if (right - left < hspace) {
6656 scrollx = (right + left) / 2 - hspace / 2;
6657 } else {
6658 if (dir < 0) {
6659 scrollx = right - hspace;
6660 } else {
6661 scrollx = left;
6662 }
6663 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006664 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006665 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006666 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006667 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006668 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006669 }
6670
6671 if (ht < vspace) {
6672 scrolly = 0;
6673 } else {
6674 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6675 scrolly = ht - vspace;
6676 } else {
6677 scrolly = 0;
6678 }
6679 }
6680
6681 if (scrollx != mScrollX || scrolly != mScrollY) {
6682 scrollTo(scrollx, scrolly);
6683 return true;
6684 } else {
6685 return false;
6686 }
6687 }
6688
6689 /**
6690 * Move the point, specified by the offset, into the view if it is needed.
6691 * This has to be called after layout. Returns true if anything changed.
6692 */
6693 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006694 if (isLayoutRequested()) {
6695 mDeferScroll = offset;
6696 return false;
6697 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006698 boolean changed = false;
6699
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006700 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006701
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006702 if (layout == null) return changed;
6703
6704 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006706 int grav;
6707
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006708 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006709 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006710 grav = 1;
6711 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006712 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006713 grav = -1;
6714 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006715 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006716 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006717 break;
6718 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006719 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006720 break;
6721 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006722 default:
6723 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006724 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006725 }
6726
Raph Levienafe8e9b2012-12-19 16:09:32 -08006727 // We only want to clamp the cursor to fit within the layout width
6728 // in left-to-right modes, because in a right to left alignment,
6729 // we want to scroll to keep the line-right on the screen, as other
6730 // lines are likely to have text flush with the right margin, which
6731 // we want to keep visible.
6732 // A better long-term solution would probably be to measure both
6733 // the full line and a blank-trimmed version, and, for example, use
6734 // the latter measurement for centering and right alignment, but for
6735 // the time being we only implement the cursor clamping in left to
6736 // right where it is most likely to be annoying.
6737 final boolean clamped = grav > 0;
6738 // FIXME: Is it okay to truncate this, or should we round?
6739 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6740 final int top = layout.getLineTop(line);
6741 final int bottom = layout.getLineTop(line + 1);
6742
6743 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6744 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6745 int ht = layout.getHeight();
6746
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006747 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6748 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08006749 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6750 // If cursor has been clamped, make sure we don't scroll.
6751 right = Math.max(x, left + hspace);
6752 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006753
6754 int hslack = (bottom - top) / 2;
6755 int vslack = hslack;
6756
6757 if (vslack > vspace / 4)
6758 vslack = vspace / 4;
6759 if (hslack > hspace / 4)
6760 hslack = hspace / 4;
6761
6762 int hs = mScrollX;
6763 int vs = mScrollY;
6764
6765 if (top - vs < vslack)
6766 vs = top - vslack;
6767 if (bottom - vs > vspace - vslack)
6768 vs = bottom - (vspace - vslack);
6769 if (ht - vs < vspace)
6770 vs = ht - vspace;
6771 if (0 - vs > 0)
6772 vs = 0;
6773
6774 if (grav != 0) {
6775 if (x - hs < hslack) {
6776 hs = x - hslack;
6777 }
6778 if (x - hs > hspace - hslack) {
6779 hs = x - (hspace - hslack);
6780 }
6781 }
6782
6783 if (grav < 0) {
6784 if (left - hs > 0)
6785 hs = left;
6786 if (right - hs < hspace)
6787 hs = right - hspace;
6788 } else if (grav > 0) {
6789 if (right - hs < hspace)
6790 hs = right - hspace;
6791 if (left - hs > 0)
6792 hs = left;
6793 } else /* grav == 0 */ {
6794 if (right - left <= hspace) {
6795 /*
6796 * If the entire text fits, center it exactly.
6797 */
6798 hs = left - (hspace - (right - left)) / 2;
6799 } else if (x > right - hslack) {
6800 /*
6801 * If we are near the right edge, keep the right edge
6802 * at the edge of the view.
6803 */
6804 hs = right - hspace;
6805 } else if (x < left + hslack) {
6806 /*
6807 * If we are near the left edge, keep the left edge
6808 * at the edge of the view.
6809 */
6810 hs = left;
6811 } else if (left > hs) {
6812 /*
6813 * Is there whitespace visible at the left? Fix it if so.
6814 */
6815 hs = left;
6816 } else if (right < hs + hspace) {
6817 /*
6818 * Is there whitespace visible at the right? Fix it if so.
6819 */
6820 hs = right - hspace;
6821 } else {
6822 /*
6823 * Otherwise, float as needed.
6824 */
6825 if (x - hs < hslack) {
6826 hs = x - hslack;
6827 }
6828 if (x - hs > hspace - hslack) {
6829 hs = x - (hspace - hslack);
6830 }
6831 }
6832 }
6833
6834 if (hs != mScrollX || vs != mScrollY) {
6835 if (mScroller == null) {
6836 scrollTo(hs, vs);
6837 } else {
6838 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6839 int dx = hs - mScrollX;
6840 int dy = vs - mScrollY;
6841
6842 if (duration > ANIMATED_SCROLL_GAP) {
6843 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006844 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006845 invalidate();
6846 } else {
6847 if (!mScroller.isFinished()) {
6848 mScroller.abortAnimation();
6849 }
6850
6851 scrollBy(dx, dy);
6852 }
6853
6854 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6855 }
6856
6857 changed = true;
6858 }
6859
6860 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006861 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6862 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006863
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006864 // The offsets here are to ensure the rectangle we are using is
6865 // within our view bounds, in case the cursor is on the far left
6866 // or right. If it isn't withing the bounds, then this request
6867 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006868 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006869 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006870 getInterestingRect(mTempRect, line);
6871 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006872
Gilles Debunne716dbf62011-03-07 18:12:10 -08006873 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006874 changed = true;
6875 }
6876 }
6877
6878 return changed;
6879 }
6880
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006881 /**
6882 * Move the cursor, if needed, so that it is at an offset that is visible
6883 * to the user. This will not move the cursor if it represents more than
6884 * one character (a selection range). This will only work if the
6885 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006886 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006887 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006888 */
6889 public boolean moveCursorToVisibleOffset() {
6890 if (!(mText instanceof Spannable)) {
6891 return false;
6892 }
Gilles Debunne05336272010-07-09 20:13:45 -07006893 int start = getSelectionStart();
6894 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006895 if (start != end) {
6896 return false;
6897 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006898
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006899 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006900
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006901 int line = mLayout.getLineForOffset(start);
6902
6903 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006904 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006905 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6906 int vslack = (bottom - top) / 2;
6907 if (vslack > vspace / 4)
6908 vslack = vspace / 4;
6909 final int vs = mScrollY;
6910
6911 if (top < (vs+vslack)) {
6912 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6913 } else if (bottom > (vspace+vs-vslack)) {
6914 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6915 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006916
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006917 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006918
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006919 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6920 final int hs = mScrollX;
6921 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6922 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006923
Doug Feltc982f602010-05-25 11:51:40 -07006924 // line might contain bidirectional text
6925 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6926 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6927
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006928 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006929 if (newStart < lowChar) {
6930 newStart = lowChar;
6931 } else if (newStart > highChar) {
6932 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006933 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006934
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006935 if (newStart != start) {
6936 Selection.setSelection((Spannable)mText, newStart);
6937 return true;
6938 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006939
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006940 return false;
6941 }
6942
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006943 @Override
6944 public void computeScroll() {
6945 if (mScroller != null) {
6946 if (mScroller.computeScrollOffset()) {
6947 mScrollX = mScroller.getCurrX();
6948 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006949 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006950 postInvalidate(); // So we draw again
6951 }
6952 }
6953 }
6954
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006955 private void getInterestingRect(Rect r, int line) {
6956 convertFromViewportToContentCoordinates(r);
6957
6958 // Rectangle can can be expanded on first and last line to take
6959 // padding into account.
6960 // TODO Take left/right padding into account too?
6961 if (line == 0) r.top -= getExtendedPaddingTop();
6962 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6963 }
6964
6965 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006966 final int horizontalOffset = viewportToContentHorizontalOffset();
6967 r.left += horizontalOffset;
6968 r.right += horizontalOffset;
6969
6970 final int verticalOffset = viewportToContentVerticalOffset();
6971 r.top += verticalOffset;
6972 r.bottom += verticalOffset;
6973 }
6974
Gilles Debunned88876a2012-03-16 17:34:04 -07006975 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006976 return getCompoundPaddingLeft() - mScrollX;
6977 }
6978
Gilles Debunned88876a2012-03-16 17:34:04 -07006979 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006980 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006981 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006982 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006983 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006984 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006985 }
6986
6987 @Override
6988 public void debug(int depth) {
6989 super.debug(depth);
6990
6991 String output = debugIndent(depth);
6992 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6993 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6994 + "} ";
6995
6996 if (mText != null) {
6997
6998 output += "mText=\"" + mText + "\" ";
6999 if (mLayout != null) {
7000 output += "mLayout width=" + mLayout.getWidth()
7001 + " height=" + mLayout.getHeight();
7002 }
7003 } else {
7004 output += "mText=NULL";
7005 }
7006 Log.d(VIEW_LOG_TAG, output);
7007 }
7008
7009 /**
7010 * Convenience for {@link Selection#getSelectionStart}.
7011 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007012 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007013 public int getSelectionStart() {
7014 return Selection.getSelectionStart(getText());
7015 }
7016
7017 /**
7018 * Convenience for {@link Selection#getSelectionEnd}.
7019 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007020 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007021 public int getSelectionEnd() {
7022 return Selection.getSelectionEnd(getText());
7023 }
7024
7025 /**
7026 * Return true iff there is a selection inside this text view.
7027 */
7028 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007029 final int selectionStart = getSelectionStart();
7030 final int selectionEnd = getSelectionEnd();
7031
7032 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007033 }
7034
7035 /**
7036 * Sets the properties of this field (lines, horizontally scrolling,
7037 * transformation method) to be for a single-line input.
7038 *
7039 * @attr ref android.R.styleable#TextView_singleLine
7040 */
7041 public void setSingleLine() {
7042 setSingleLine(true);
7043 }
7044
7045 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007046 * Sets the properties of this field to transform input to ALL CAPS
7047 * display. This may use a "small caps" formatting if available.
7048 * This setting will be ignored if this field is editable or selectable.
7049 *
7050 * This call replaces the current transformation method. Disabling this
7051 * will not necessarily restore the previous behavior from before this
7052 * was enabled.
7053 *
7054 * @see #setTransformationMethod(TransformationMethod)
7055 * @attr ref android.R.styleable#TextView_textAllCaps
7056 */
7057 public void setAllCaps(boolean allCaps) {
7058 if (allCaps) {
7059 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7060 } else {
7061 setTransformationMethod(null);
7062 }
7063 }
7064
7065 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007066 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7067 * transformation method) to be for a single-line input; if false, restores these to the default
7068 * conditions.
7069 *
7070 * Note that the default conditions are not necessarily those that were in effect prior this
7071 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007072 *
7073 * @attr ref android.R.styleable#TextView_singleLine
7074 */
7075 @android.view.RemotableViewMethod
7076 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007077 // Could be used, but may break backward compatibility.
7078 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007079 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007080 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007081 }
7082
7083 /**
7084 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7085 * @param singleLine
7086 */
7087 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007088 if (mEditor != null &&
7089 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007090 if (singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007091 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007092 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007093 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007094 }
7095 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007096 }
7097
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007098 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7099 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007100 mSingleLine = singleLine;
7101 if (singleLine) {
7102 setLines(1);
7103 setHorizontallyScrolling(true);
7104 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007105 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007106 }
7107 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007108 if (changeMaxLines) {
7109 setMaxLines(Integer.MAX_VALUE);
7110 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007111 setHorizontallyScrolling(false);
7112 if (applyTransformation) {
7113 setTransformationMethod(null);
7114 }
7115 }
7116 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007117
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007118 /**
7119 * Causes words in the text that are longer than the view is wide
7120 * to be ellipsized instead of broken in the middle. You may also
7121 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007122 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007123 * to turn off ellipsizing.
7124 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007125 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07007126 * {@link android.text.TextUtils.TruncateAt#END} and
7127 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7128 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007129 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007130 * @attr ref android.R.styleable#TextView_ellipsize
7131 */
7132 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007133 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7134 if (mEllipsize != where) {
7135 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007136
Gilles Debunne22378292011-08-12 10:38:52 -07007137 if (mLayout != null) {
7138 nullLayouts();
7139 requestLayout();
7140 invalidate();
7141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007142 }
7143 }
7144
7145 /**
7146 * Sets how many times to repeat the marquee animation. Only applied if the
7147 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7148 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007149 * @see #getMarqueeRepeatLimit()
7150 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007151 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7152 */
7153 public void setMarqueeRepeatLimit(int marqueeLimit) {
7154 mMarqueeRepeatLimit = marqueeLimit;
7155 }
7156
7157 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007158 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7159 * TextView has marquee enabled.
7160 *
7161 * @return the number of times the marquee animation is repeated. -1 if the animation
7162 * repeats indefinitely
7163 *
7164 * @see #setMarqueeRepeatLimit(int)
7165 *
7166 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7167 */
7168 public int getMarqueeRepeatLimit() {
7169 return mMarqueeRepeatLimit;
7170 }
7171
7172 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007173 * Returns where, if anywhere, words that are longer than the view
7174 * is wide should be ellipsized.
7175 */
7176 @ViewDebug.ExportedProperty
7177 public TextUtils.TruncateAt getEllipsize() {
7178 return mEllipsize;
7179 }
7180
7181 /**
7182 * Set the TextView so that when it takes focus, all the text is
7183 * selected.
7184 *
7185 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7186 */
7187 @android.view.RemotableViewMethod
7188 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007189 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007190 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007191
7192 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7193 setText(mText, BufferType.SPANNABLE);
7194 }
7195 }
7196
7197 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007198 * Set whether the cursor is visible. The default is true. Note that this property only
7199 * makes sense for editable TextView.
7200 *
7201 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007202 *
7203 * @attr ref android.R.styleable#TextView_cursorVisible
7204 */
7205 @android.view.RemotableViewMethod
7206 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007207 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007208 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007209 if (mEditor.mCursorVisible != visible) {
7210 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007211 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007212
Gilles Debunne2d373a12012-04-20 15:32:19 -07007213 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007214
Gilles Debunne3d010062011-02-18 14:16:41 -08007215 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007216 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007217 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007218 }
7219
Gilles Debunnef03acef2012-04-30 19:26:19 -07007220 /**
7221 * @return whether or not the cursor is visible (assuming this TextView is editable)
7222 *
7223 * @see #setCursorVisible(boolean)
7224 *
7225 * @attr ref android.R.styleable#TextView_cursorVisible
7226 */
7227 public boolean isCursorVisible() {
7228 // true is the default value
7229 return mEditor == null ? true : mEditor.mCursorVisible;
7230 }
7231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007232 private boolean canMarquee() {
7233 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007234 return width > 0 && (mLayout.getLineWidth(0) > width ||
7235 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7236 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007237 }
7238
7239 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007240 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007241 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007242
Romain Guy939151f2009-04-08 14:22:40 -07007243 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7244 return;
7245 }
7246
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007247 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7248 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007249
Adam Powell282e3772011-08-30 16:51:11 -07007250 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7251 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7252 final Layout tmp = mLayout;
7253 mLayout = mSavedMarqueeModeLayout;
7254 mSavedMarqueeModeLayout = tmp;
7255 setHorizontalFadingEdgeEnabled(true);
7256 requestLayout();
7257 invalidate();
7258 }
7259
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007260 if (mMarquee == null) mMarquee = new Marquee(this);
7261 mMarquee.start(mMarqueeRepeatLimit);
7262 }
7263 }
7264
7265 private void stopMarquee() {
7266 if (mMarquee != null && !mMarquee.isStopped()) {
7267 mMarquee.stop();
7268 }
Adam Powell282e3772011-08-30 16:51:11 -07007269
7270 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7271 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7272 final Layout tmp = mSavedMarqueeModeLayout;
7273 mSavedMarqueeModeLayout = mLayout;
7274 mLayout = tmp;
7275 setHorizontalFadingEdgeEnabled(false);
7276 requestLayout();
7277 invalidate();
7278 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007279 }
7280
7281 private void startStopMarquee(boolean start) {
7282 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7283 if (start) {
7284 startMarquee();
7285 } else {
7286 stopMarquee();
7287 }
7288 }
7289 }
7290
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007291 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007292 * This method is called when the text is changed, in case any subclasses
7293 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007294 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007295 * Within <code>text</code>, the <code>lengthAfter</code> characters
7296 * beginning at <code>start</code> have just replaced old text that had
7297 * length <code>lengthBefore</code>. It is an error to attempt to make
7298 * changes to <code>text</code> from this callback.
7299 *
7300 * @param text The text the TextView is displaying
7301 * @param start The offset of the start of the range of the text that was
7302 * modified
7303 * @param lengthBefore The length of the former text that has been replaced
7304 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007305 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007306 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007307 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007308 }
7309
7310 /**
7311 * This method is called when the selection has changed, in case any
7312 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007313 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007314 * @param selStart The new selection start location.
7315 * @param selEnd The new selection end location.
7316 */
7317 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007318 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007319 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007320
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007321 /**
7322 * Adds a TextWatcher to the list of those whose methods are called
7323 * whenever this TextView's text changes.
7324 * <p>
7325 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7326 * not called after {@link #setText} calls. Now, doing {@link #setText}
7327 * if there are any text changed listeners forces the buffer type to
7328 * Editable if it would not otherwise be and does call this method.
7329 */
7330 public void addTextChangedListener(TextWatcher watcher) {
7331 if (mListeners == null) {
7332 mListeners = new ArrayList<TextWatcher>();
7333 }
7334
7335 mListeners.add(watcher);
7336 }
7337
7338 /**
7339 * Removes the specified TextWatcher from the list of those whose
7340 * methods are called
7341 * whenever this TextView's text changes.
7342 */
7343 public void removeTextChangedListener(TextWatcher watcher) {
7344 if (mListeners != null) {
7345 int i = mListeners.indexOf(watcher);
7346
7347 if (i >= 0) {
7348 mListeners.remove(i);
7349 }
7350 }
7351 }
7352
Gilles Debunne6435a562011-08-04 21:22:30 -07007353 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007354 if (mListeners != null) {
7355 final ArrayList<TextWatcher> list = mListeners;
7356 final int count = list.size();
7357 for (int i = 0; i < count; i++) {
7358 list.get(i).beforeTextChanged(text, start, before, after);
7359 }
7360 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007361
7362 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007363 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7364 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07007365 }
7366
7367 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007368 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007369 if (!(mText instanceof Editable)) return;
7370 Editable text = (Editable) mText;
7371
7372 T[] spans = text.getSpans(start, end, type);
7373 final int length = spans.length;
7374 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007375 final int spanStart = text.getSpanStart(spans[i]);
7376 final int spanEnd = text.getSpanEnd(spans[i]);
7377 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07007378 text.removeSpan(spans[i]);
7379 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007380 }
7381
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007382 void removeAdjacentSuggestionSpans(final int pos) {
7383 if (!(mText instanceof Editable)) return;
7384 final Editable text = (Editable) mText;
7385
7386 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7387 final int length = spans.length;
7388 for (int i = 0; i < length; i++) {
7389 final int spanStart = text.getSpanStart(spans[i]);
7390 final int spanEnd = text.getSpanEnd(spans[i]);
7391 if (spanEnd == pos || spanStart == pos) {
7392 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7393 text.removeSpan(spans[i]);
7394 }
7395 }
7396 }
7397 }
7398
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007399 /**
7400 * Not private so it can be called from an inner class without going
7401 * through a thunk.
7402 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007403 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007404 if (mListeners != null) {
7405 final ArrayList<TextWatcher> list = mListeners;
7406 final int count = list.size();
7407 for (int i = 0; i < count; i++) {
7408 list.get(i).onTextChanged(text, start, before, after);
7409 }
7410 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007411
Gilles Debunne2d373a12012-04-20 15:32:19 -07007412 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007413 }
7414
7415 /**
7416 * Not private so it can be called from an inner class without going
7417 * through a thunk.
7418 */
7419 void sendAfterTextChanged(Editable text) {
7420 if (mListeners != null) {
7421 final ArrayList<TextWatcher> list = mListeners;
7422 final int count = list.size();
7423 for (int i = 0; i < count; i++) {
7424 list.get(i).afterTextChanged(text);
7425 }
7426 }
7427 }
7428
Gilles Debunned88876a2012-03-16 17:34:04 -07007429 void updateAfterEdit() {
7430 invalidate();
7431 int curs = getSelectionStart();
7432
7433 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7434 registerForPreDraw();
7435 }
7436
Raph Levienf5c1a872012-10-15 17:22:26 -07007437 checkForResize();
7438
Gilles Debunned88876a2012-03-16 17:34:04 -07007439 if (curs >= 0) {
7440 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007441 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007442 bringPointIntoView(curs);
7443 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007444 }
7445
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007446 /**
7447 * Not private so it can be called from an inner class without going
7448 * through a thunk.
7449 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007450 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007451 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007452 if (ims == null || ims.mBatchEditNesting == 0) {
7453 updateAfterEdit();
7454 }
7455 if (ims != null) {
7456 ims.mContentChanged = true;
7457 if (ims.mChangedStart < 0) {
7458 ims.mChangedStart = start;
7459 ims.mChangedEnd = start+before;
7460 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007461 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7462 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007463 }
7464 ims.mChangedDelta += after-before;
7465 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007466
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007467 sendOnTextChanged(buffer, start, before, after);
7468 onTextChanged(buffer, start, before, after);
7469 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007471 /**
7472 * Not private so it can be called from an inner class without going
7473 * through a thunk.
7474 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007475 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007476 // XXX Make the start and end move together if this ends up
7477 // spending too much time invalidating.
7478
7479 boolean selChanged = false;
7480 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007481
Gilles Debunne2d373a12012-04-20 15:32:19 -07007482 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007484 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007485 selChanged = true;
7486 newSelEnd = newStart;
7487
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007488 if (oldStart >= 0 || newStart >= 0) {
7489 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007490 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007491 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007492 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007493 }
7494 }
7495
7496 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007497 selChanged = true;
7498 newSelStart = newStart;
7499
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007500 if (oldStart >= 0 || newStart >= 0) {
7501 int end = Selection.getSelectionEnd(buf);
7502 invalidateCursor(end, oldStart, newStart);
7503 }
7504 }
7505
7506 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007507 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007508 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007509
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007510 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7511 if (newSelStart < 0) {
7512 newSelStart = Selection.getSelectionStart(buf);
7513 }
7514 if (newSelEnd < 0) {
7515 newSelEnd = Selection.getSelectionEnd(buf);
7516 }
7517 onSelectionChanged(newSelStart, newSelEnd);
7518 }
7519 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007520
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007521 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7522 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007523 if (ims == null || ims.mBatchEditNesting == 0) {
7524 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007525 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007526 checkForResize();
7527 } else {
7528 ims.mContentChanged = true;
7529 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007530 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007531 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7532 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007533 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007534 }
7535
7536 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007537 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007538 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7539 ims.mSelectionModeChanged = true;
7540 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007541
7542 if (Selection.getSelectionStart(buf) >= 0) {
7543 if (ims == null || ims.mBatchEditNesting == 0) {
7544 invalidateCursor();
7545 } else {
7546 ims.mCursorChanged = true;
7547 }
7548 }
7549 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007550
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007551 if (what instanceof ParcelableSpan) {
7552 // If this is a span that can be sent to a remote process,
7553 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007554 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007555 if (ims.mBatchEditNesting != 0) {
7556 if (oldStart >= 0) {
7557 if (ims.mChangedStart > oldStart) {
7558 ims.mChangedStart = oldStart;
7559 }
7560 if (ims.mChangedStart > oldEnd) {
7561 ims.mChangedStart = oldEnd;
7562 }
7563 }
7564 if (newStart >= 0) {
7565 if (ims.mChangedStart > newStart) {
7566 ims.mChangedStart = newStart;
7567 }
7568 if (ims.mChangedStart > newEnd) {
7569 ims.mChangedStart = newEnd;
7570 }
7571 }
7572 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007573 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007574 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007575 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007576 ims.mContentChanged = true;
7577 }
7578 }
7579 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007580
Gilles Debunne2d373a12012-04-20 15:32:19 -07007581 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7582 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007583 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007584 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007585 }
7586
Gilles Debunne6435a562011-08-04 21:22:30 -07007587 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007588 * @hide
7589 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007590 @Override
Romain Guya440b002010-02-24 15:57:54 -08007591 public void dispatchFinishTemporaryDetach() {
7592 mDispatchTemporaryDetach = true;
7593 super.dispatchFinishTemporaryDetach();
7594 mDispatchTemporaryDetach = false;
7595 }
7596
7597 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007598 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007599 super.onStartTemporaryDetach();
7600 // Only track when onStartTemporaryDetach() is called directly,
7601 // usually because this instance is an editable field in a list
7602 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007603
Adam Powell057a5852012-05-11 10:28:38 -07007604 // Tell the editor that we are temporarily detached. It can use this to preserve
7605 // selection state as needed.
7606 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007607 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007608
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007609 @Override
7610 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007611 super.onFinishTemporaryDetach();
7612 // Only track when onStartTemporaryDetach() is called directly,
7613 // usually because this instance is an editable field in a list
7614 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007615 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007616 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007617
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007618 @Override
7619 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7620 if (mTemporaryDetach) {
7621 // If we are temporarily in the detach state, then do nothing.
7622 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7623 return;
7624 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007625
Gilles Debunne2d373a12012-04-20 15:32:19 -07007626 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007627
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007628 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007629 if (mText instanceof Spannable) {
7630 Spannable sp = (Spannable) mText;
7631 MetaKeyKeyListener.resetMetaState(sp);
7632 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007633 }
7634
7635 startStopMarquee(focused);
7636
7637 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007638 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007639 }
7640
7641 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7642 }
7643
7644 @Override
7645 public void onWindowFocusChanged(boolean hasWindowFocus) {
7646 super.onWindowFocusChanged(hasWindowFocus);
7647
Gilles Debunne2d373a12012-04-20 15:32:19 -07007648 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007649
7650 startStopMarquee(hasWindowFocus);
7651 }
7652
Adam Powellba0a2c32010-09-28 17:41:23 -07007653 @Override
7654 protected void onVisibilityChanged(View changedView, int visibility) {
7655 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007656 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007657 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007658 }
7659 }
7660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007661 /**
7662 * Use {@link BaseInputConnection#removeComposingSpans
7663 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7664 * state from this text view.
7665 */
7666 public void clearComposingText() {
7667 if (mText instanceof Spannable) {
7668 BaseInputConnection.removeComposingSpans((Spannable)mText);
7669 }
7670 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007671
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007672 @Override
7673 public void setSelected(boolean selected) {
7674 boolean wasSelected = isSelected();
7675
7676 super.setSelected(selected);
7677
7678 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7679 if (selected) {
7680 startMarquee();
7681 } else {
7682 stopMarquee();
7683 }
7684 }
7685 }
7686
7687 @Override
7688 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007689 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007690
Gilles Debunne2d373a12012-04-20 15:32:19 -07007691 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007693 final boolean superResult = super.onTouchEvent(event);
7694
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007695 /*
7696 * Don't handle the release after a long press, because it will
7697 * move the selection away from whatever the menu action was
7698 * trying to affect.
7699 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007700 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7701 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007702 return superResult;
7703 }
7704
Gilles Debunne70a63122011-09-01 13:27:33 -07007705 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007706 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007707
Gilles Debunne70a63122011-09-01 13:27:33 -07007708 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007709 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007710 boolean handled = false;
7711
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007712 if (mMovement != null) {
7713 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7714 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007715
Gilles Debunne60e21862012-01-30 15:04:14 -08007716 final boolean textIsSelectable = isTextSelectable();
7717 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007718 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007719 // on non editable text that support text selection.
7720 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007721 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7722 getSelectionEnd(), ClickableSpan.class);
7723
Gilles Debunne822b8f02012-01-17 18:02:15 -08007724 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007725 links[0].onClick(this);
7726 handled = true;
7727 }
7728 }
7729
Gilles Debunne60e21862012-01-30 15:04:14 -08007730 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007731 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007732 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007733 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007734 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007735 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007736 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007737
Gilles Debunned88876a2012-03-16 17:34:04 -07007738 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007739 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007740
7741 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007742 }
7743
The Android Open Source Project4df24232009-03-05 14:34:35 -08007744 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007745 return true;
7746 }
7747 }
7748
7749 return superResult;
7750 }
7751
Jeff Brown8f345672011-02-26 13:29:53 -08007752 @Override
7753 public boolean onGenericMotionEvent(MotionEvent event) {
7754 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7755 try {
7756 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7757 return true;
7758 }
7759 } catch (AbstractMethodError ex) {
7760 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7761 // Ignore its absence in case third party applications implemented the
7762 // interface directly.
7763 }
7764 }
7765 return super.onGenericMotionEvent(event);
7766 }
7767
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007768 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007769 * @return True iff this TextView contains a text that can be edited, or if this is
7770 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007771 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007772 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007773 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007774 }
7775
The Android Open Source Project4df24232009-03-05 14:34:35 -08007776 /**
7777 * Returns true, only while processing a touch gesture, if the initial
7778 * 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 -07007779 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007780 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007781 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007782 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007783 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007784 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007785
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007786 @Override
7787 public void cancelLongPress() {
7788 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007789 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007790 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007791
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007792 @Override
7793 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007794 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007795 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7796 return true;
7797 }
7798 }
7799
7800 return super.onTrackballEvent(event);
7801 }
7802
7803 public void setScroller(Scroller s) {
7804 mScroller = s;
7805 }
7806
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007807 @Override
7808 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007809 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7810 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007811 if (mMarquee != null && !mMarquee.isStopped()) {
7812 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007813 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007814 final float scroll = marquee.getScroll();
7815 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007816 } else {
7817 return 0.0f;
7818 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007819 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007820 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007821 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007822 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007823 case Gravity.LEFT:
7824 return 0.0f;
7825 case Gravity.RIGHT:
7826 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7827 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7828 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7829 case Gravity.CENTER_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07007830 case Gravity.FILL_HORIZONTAL:
7831 final int textDirection = mLayout.getParagraphDirection(0);
7832 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
7833 return 0.0f;
7834 } else {
7835 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7836 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7837 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7838 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007839 }
7840 }
7841 }
7842 return super.getLeftFadingEdgeStrength();
7843 }
7844
7845 @Override
7846 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007847 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7848 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007849 if (mMarquee != null && !mMarquee.isStopped()) {
7850 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007851 final float maxFadeScroll = marquee.getMaxFadeScroll();
7852 final float scroll = marquee.getScroll();
7853 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007854 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007855 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007856 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007857 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007858 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007859 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7860 getCompoundPaddingRight();
7861 final float lineWidth = mLayout.getLineWidth(0);
7862 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007863 case Gravity.RIGHT:
7864 return 0.0f;
7865 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007866 case Gravity.FILL_HORIZONTAL:
Raph Levien8079ae12013-09-26 15:57:07 -07007867 final int textDirection = mLayout.getParagraphDirection(0);
7868 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
7869 return 0.0f;
7870 } else {
7871 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007872 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7873 getHorizontalFadingEdgeLength();
Raph Levien8079ae12013-09-26 15:57:07 -07007874 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007875 }
7876 }
7877 }
7878 return super.getRightFadingEdgeStrength();
7879 }
7880
7881 @Override
7882 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007883 if (mLayout != null) {
7884 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7885 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7886 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007887
7888 return super.computeHorizontalScrollRange();
7889 }
7890
7891 @Override
7892 protected int computeVerticalScrollRange() {
7893 if (mLayout != null)
7894 return mLayout.getHeight();
7895
7896 return super.computeVerticalScrollRange();
7897 }
7898
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007899 @Override
7900 protected int computeVerticalScrollExtent() {
7901 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7902 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007903
7904 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007905 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7906 super.findViewsWithText(outViews, searched, flags);
7907 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7908 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7909 String searchedLowerCase = searched.toString().toLowerCase();
7910 String textLowerCase = mText.toString().toLowerCase();
7911 if (textLowerCase.contains(searchedLowerCase)) {
7912 outViews.add(this);
7913 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007914 }
7915 }
7916
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007917 public enum BufferType {
7918 NORMAL, SPANNABLE, EDITABLE,
7919 }
7920
7921 /**
7922 * Returns the TextView_textColor attribute from the
John Spurlock330dd532012-12-18 12:03:11 -05007923 * TypedArray, if set, or the TextAppearance_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007924 * from the TextView_textAppearance attribute, if TextView_textColor
7925 * was not set directly.
7926 */
7927 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7928 ColorStateList colors;
7929 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7930 TextView_textColor);
7931
7932 if (colors == null) {
7933 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7934 TextView_textAppearance, -1);
7935 if (ap != -1) {
7936 TypedArray appearance;
7937 appearance = context.obtainStyledAttributes(ap,
7938 com.android.internal.R.styleable.TextAppearance);
7939 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7940 TextAppearance_textColor);
7941 appearance.recycle();
7942 }
7943 }
7944
7945 return colors;
7946 }
7947
7948 /**
7949 * Returns the default color from the TextView_textColor attribute
7950 * from the AttributeSet, if set, or the default color from the
7951 * TextAppearance_textColor from the TextView_textAppearance attribute,
7952 * if TextView_textColor was not set directly.
7953 */
7954 public static int getTextColor(Context context,
7955 TypedArray attrs,
7956 int def) {
7957 ColorStateList colors = getTextColors(context, attrs);
7958
7959 if (colors == null) {
7960 return def;
7961 } else {
7962 return colors.getDefaultColor();
7963 }
7964 }
7965
7966 @Override
7967 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007968 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7969 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7970 switch (keyCode) {
7971 case KeyEvent.KEYCODE_A:
7972 if (canSelectText()) {
7973 return onTextContextMenuItem(ID_SELECT_ALL);
7974 }
7975 break;
7976 case KeyEvent.KEYCODE_X:
7977 if (canCut()) {
7978 return onTextContextMenuItem(ID_CUT);
7979 }
7980 break;
7981 case KeyEvent.KEYCODE_C:
7982 if (canCopy()) {
7983 return onTextContextMenuItem(ID_COPY);
7984 }
7985 break;
7986 case KeyEvent.KEYCODE_V:
7987 if (canPaste()) {
7988 return onTextContextMenuItem(ID_PASTE);
7989 }
7990 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007991 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007992 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007993 return super.onKeyShortcut(keyCode, event);
7994 }
7995
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007996 /**
7997 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7998 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07007999 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8000 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008001 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07008002 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008003 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008004 }
8005
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008006 /**
8007 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8008 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07008009 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08008010 * See also {@link #canSelectText()}.
8011 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008012 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008013 // prepareCursorController() relies on this method.
8014 // If you change this condition, make sure prepareCursorController is called anywhere
8015 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008016 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008017 return isTextEditable() ||
8018 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008019 }
8020
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008021 private Locale getTextServicesLocale(boolean allowNullLocale) {
8022 // Start fetching the text services locale asynchronously.
8023 updateTextServicesLocaleAsync();
8024 // If !allowNullLocale and there is no cached text services locale, just return the default
8025 // locale.
8026 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8027 : mCurrentSpellCheckerLocaleCache;
8028 }
8029
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008030 /**
8031 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008032 * Caveat: This method may not return the latest text services locale, but this should be
8033 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008034 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008035 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008036 * in this TextView, based on the current spell checker settings,
8037 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008038 * Please note that a word iterator in this TextView is different from another word iterator
8039 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008040 * @hide
8041 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008042 // TODO: Support multi-locale
8043 // TODO: Update the text services locale immediately after the keyboard locale is switched
8044 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008045 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008046 return getTextServicesLocale(false /* allowNullLocale */);
8047 }
8048
8049 /**
8050 * This is a temporary method. Future versions may support multi-locale text.
8051 * Caveat: This method may not return the latest spell checker locale, but this should be
8052 * acceptable and it's more important to make this method asynchronous.
8053 *
8054 * @return The locale that should be used for a spell checker in this TextView,
8055 * based on the current spell checker settings, the current IME's locale, or the system default
8056 * locale.
8057 * @hide
8058 */
8059 public Locale getSpellCheckerLocale() {
8060 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008061 }
8062
8063 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008064 // AsyncTask.execute() uses a serial executor which means we don't have
8065 // to lock around updateTextServicesLocaleLocked() to prevent it from
8066 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008067 AsyncTask.execute(new Runnable() {
8068 @Override
8069 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008070 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008071 }
8072 });
8073 }
8074
8075 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008076 final TextServicesManager textServicesManager = (TextServicesManager)
8077 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8078 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008079 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008080 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09008081 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008082 } else {
8083 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008084 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008085 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008086 }
8087
8088 void onLocaleChanged() {
8089 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008090 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008091 }
8092
8093 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008094 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8095 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008096 * @hide
8097 */
8098 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008099 if (mEditor != null) {
8100 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008101 } else {
8102 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008103 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008104 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008105
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008106 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07008107 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008108 super.onPopulateAccessibilityEvent(event);
8109
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08008110 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07008111 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8112 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07008113 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07008114 event.getText().add(text);
8115 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008116 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008117 }
8118
alanv7d624192012-05-21 14:23:17 -07008119 /**
8120 * @return true if the user has explicitly allowed accessibility services
8121 * to speak passwords.
8122 */
8123 private boolean shouldSpeakPasswordsForAccessibility() {
8124 return (Settings.Secure.getInt(mContext.getContentResolver(),
8125 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
8126 }
8127
Svetoslav Ganov30401322011-05-12 18:53:45 -07008128 @Override
8129 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8130 super.onInitializeAccessibilityEvent(event);
8131
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008132 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07008133 final boolean isPassword = hasPasswordTransformationMethod();
8134 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07008135
8136 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8137 event.setFromIndex(Selection.getSelectionStart(mText));
8138 event.setToIndex(Selection.getSelectionEnd(mText));
8139 event.setItemCount(mText.length());
8140 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07008141 }
8142
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008143 @Override
8144 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8145 super.onInitializeAccessibilityNodeInfo(info);
8146
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008147 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008148 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008149 info.setPassword(isPassword);
8150
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008151 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008152 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008153 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008154
Svetoslavbcc46a02013-02-06 11:56:00 -08008155 if (mBufferType == BufferType.EDITABLE) {
8156 info.setEditable(true);
8157 }
8158
Svetoslav6254f482013-06-04 17:22:14 -07008159 if (mEditor != null) {
8160 info.setInputType(mEditor.mInputType);
Alan Viverette5b2081d2013-08-28 10:43:07 -07008161
8162 if (mEditor.mError != null) {
8163 info.setContentInvalid(true);
8164 }
Svetoslav6254f482013-06-04 17:22:14 -07008165 }
8166
Svetoslavdb7da0e2013-04-22 18:34:02 -07008167 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008168 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8169 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8170 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8171 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8172 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8173 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8174 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8175 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008176
Svetoslav7c512842013-01-30 23:02:08 -08008177 if (isFocused()) {
8178 if (canSelectText()) {
8179 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8180 }
8181 if (canCopy()) {
8182 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8183 }
8184 if (canPaste()) {
8185 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8186 }
8187 if (canCut()) {
8188 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8189 }
8190 }
Alan Viverette058ac7c2013-08-19 16:44:30 -07008191
8192 if (!isSingleLine()) {
8193 info.setMultiLine(true);
8194 }
Svetoslav7c512842013-01-30 23:02:08 -08008195 }
8196
8197 @Override
8198 public boolean performAccessibilityAction(int action, Bundle arguments) {
8199 switch (action) {
8200 case AccessibilityNodeInfo.ACTION_COPY: {
8201 if (isFocused() && canCopy()) {
8202 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08008203 return true;
8204 }
8205 }
8206 } return false;
8207 case AccessibilityNodeInfo.ACTION_PASTE: {
8208 if (isFocused() && canPaste()) {
8209 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08008210 return true;
8211 }
8212 }
8213 } return false;
8214 case AccessibilityNodeInfo.ACTION_CUT: {
8215 if (isFocused() && canCut()) {
8216 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08008217 return true;
8218 }
8219 }
8220 } return false;
8221 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8222 if (isFocused() && canSelectText()) {
Svetoslav7c512842013-01-30 23:02:08 -08008223 CharSequence text = getIterableTextForAccessibility();
8224 if (text == null) {
8225 return false;
8226 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008227 final int start = (arguments != null) ? arguments.getInt(
8228 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8229 final int end = (arguments != null) ? arguments.getInt(
8230 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8231 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8232 // No arguments clears the selection.
8233 if (start == end && end == -1) {
8234 Selection.removeSelection((Spannable) text);
Svetoslavd0c83cc2013-02-04 18:39:59 -08008235 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008236 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008237 if (start >= 0 && start <= end && end <= text.length()) {
8238 Selection.setSelection((Spannable) text, start, end);
8239 // Make sure selection mode is engaged.
8240 if (mEditor != null) {
8241 mEditor.startSelectionActionMode();
8242 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008243 return true;
8244 }
Svetoslav7c512842013-01-30 23:02:08 -08008245 }
8246 }
8247 } return false;
8248 default: {
8249 return super.performAccessibilityAction(action, arguments);
8250 }
8251 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008252 }
8253
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008254 @Override
8255 public void sendAccessibilityEvent(int eventType) {
8256 // Do not send scroll events since first they are not interesting for
8257 // accessibility and second such events a generated too frequently.
8258 // For details see the implementation of bringTextIntoView().
8259 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8260 return;
8261 }
8262 super.sendAccessibilityEvent(eventType);
8263 }
8264
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008265 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008266 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008267 *
8268 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008269 *
8270 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008271 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008272 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008273 CharSequence text = getText();
8274 if (TextUtils.isEmpty(text)) {
8275 text = getHint();
8276 }
8277 return text;
8278 }
8279
svetoslavganov75986cf2009-05-14 22:28:01 -07008280 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8281 int fromIndex, int removedCount, int addedCount) {
8282 AccessibilityEvent event =
8283 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8284 event.setFromIndex(fromIndex);
8285 event.setRemovedCount(removedCount);
8286 event.setAddedCount(addedCount);
8287 event.setBeforeText(beforeText);
8288 sendAccessibilityEventUnchecked(event);
8289 }
8290
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008291 /**
8292 * Returns whether this text view is a current input method target. The
8293 * default implementation just checks with {@link InputMethodManager}.
8294 */
8295 public boolean isInputMethodTarget() {
8296 InputMethodManager imm = InputMethodManager.peekInstance();
8297 return imm != null && imm.isActive(this);
8298 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008299
Gilles Debunned88876a2012-03-16 17:34:04 -07008300 static final int ID_SELECT_ALL = android.R.id.selectAll;
8301 static final int ID_CUT = android.R.id.cut;
8302 static final int ID_COPY = android.R.id.copy;
8303 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008304
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008305 /**
8306 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008307 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8308 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008309 *
8310 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008311 */
8312 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008313 int min = 0;
8314 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008315
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008316 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008317 final int selStart = getSelectionStart();
8318 final int selEnd = getSelectionEnd();
8319
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008320 min = Math.max(0, Math.min(selStart, selEnd));
8321 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008322 }
8323
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008324 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008325 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008326 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008327 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008328 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008329 return true;
8330
8331 case ID_PASTE:
8332 paste(min, max);
8333 return true;
8334
8335 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008336 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008337 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008338 stopSelectionActionMode();
8339 return true;
8340
8341 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008342 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008343 stopSelectionActionMode();
8344 return true;
8345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008346 return false;
8347 }
8348
Gilles Debunned88876a2012-03-16 17:34:04 -07008349 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008350 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8351 }
8352
Gilles Debunnee15b3582010-06-16 15:17:21 -07008353 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008354 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008355 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008356
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008357 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008358 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008359 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008360
Gilles Debunned88876a2012-03-16 17:34:04 -07008361 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008362 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008363 }
8364
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008365 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008366 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008367 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008368 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008369
Gilles Debunne299733e2011-02-07 17:11:41 -08008370 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008371 }
8372
Gilles Debunne60e21862012-01-30 15:04:14 -08008373 @Override
8374 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8375 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008376 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008377 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008378 }
8379 }
8380
8381 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008382 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8383 * by the IME or by the spell checker as the user types. This is done by adding
8384 * {@link SuggestionSpan}s to the text.
8385 *
8386 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8387 * user asks for them on these parts of the text. This value depends on the inputType of this
8388 * TextView.
8389 *
8390 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8391 *
8392 * In addition, the type variation must be one of
8393 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8394 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8395 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8396 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8397 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8398 *
8399 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8400 *
8401 * @return true if the suggestions popup window is enabled, based on the inputType.
8402 */
8403 public boolean isSuggestionsEnabled() {
8404 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008405 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8406 return false;
8407 }
8408 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008409
Gilles Debunne2d373a12012-04-20 15:32:19 -07008410 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008411 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8412 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8413 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8414 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8415 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8416 }
8417
8418 /**
8419 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8420 * selection is initiated in this View.
8421 *
8422 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8423 * Paste actions, depending on what this View supports.
8424 *
8425 * A custom implementation can add new entries in the default menu in its
8426 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8427 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8428 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8429 * or {@link android.R.id#paste} ids as parameters.
8430 *
8431 * Returning false from
8432 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8433 * the action mode from being started.
8434 *
8435 * Action click events should be handled by the custom implementation of
8436 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8437 *
8438 * Note that text selection mode is not started when a TextView receives focus and the
8439 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8440 * that case, to allow for quick replacement.
8441 */
8442 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008443 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008444 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008445 }
8446
8447 /**
8448 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8449 *
8450 * @return The current custom selection callback.
8451 */
8452 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008453 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008454 }
8455
8456 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008457 * @hide
8458 */
8459 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008460 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008461 }
8462
8463 boolean canCut() {
8464 if (hasPasswordTransformationMethod()) {
8465 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008466 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008467
Gilles Debunne2d373a12012-04-20 15:32:19 -07008468 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8469 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008470 return true;
8471 }
8472
8473 return false;
8474 }
8475
8476 boolean canCopy() {
8477 if (hasPasswordTransformationMethod()) {
8478 return false;
8479 }
8480
8481 if (mText.length() > 0 && hasSelection()) {
8482 return true;
8483 }
8484
8485 return false;
8486 }
8487
8488 boolean canPaste() {
8489 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008490 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008491 getSelectionStart() >= 0 &&
8492 getSelectionEnd() >= 0 &&
8493 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8494 hasPrimaryClip());
8495 }
8496
8497 boolean selectAllText() {
8498 final int length = mText.length();
8499 Selection.setSelection((Spannable) mText, 0, length);
8500 return length > 0;
8501 }
8502
8503 /**
8504 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8505 * by [min, max] when replacing this region by paste.
8506 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8507 * make sure we do not add an extra one from the paste content.
8508 */
8509 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8510 if (paste.length() > 0) {
8511 if (min > 0) {
8512 final char charBefore = mTransformed.charAt(min - 1);
8513 final char charAfter = paste.charAt(0);
8514
8515 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8516 // Two spaces at beginning of paste: remove one
8517 final int originalLength = mText.length();
8518 deleteText_internal(min - 1, min);
8519 // Due to filters, there is no guarantee that exactly one character was
8520 // removed: count instead.
8521 final int delta = mText.length() - originalLength;
8522 min += delta;
8523 max += delta;
8524 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8525 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8526 // No space at beginning of paste: add one
8527 final int originalLength = mText.length();
8528 replaceText_internal(min, min, " ");
8529 // Taking possible filters into account as above.
8530 final int delta = mText.length() - originalLength;
8531 min += delta;
8532 max += delta;
8533 }
8534 }
8535
8536 if (max < mText.length()) {
8537 final char charBefore = paste.charAt(paste.length() - 1);
8538 final char charAfter = mTransformed.charAt(max);
8539
8540 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8541 // Two spaces at end of paste: remove one
8542 deleteText_internal(max, max + 1);
8543 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8544 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8545 // No space at end of paste: add one
8546 replaceText_internal(max, max, " ");
8547 }
8548 }
8549 }
8550
8551 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008552 }
8553
8554 /**
8555 * Paste clipboard content between min and max positions.
8556 */
8557 private void paste(int min, int max) {
8558 ClipboardManager clipboard =
8559 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8560 ClipData clip = clipboard.getPrimaryClip();
8561 if (clip != null) {
8562 boolean didFirst = false;
8563 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008564 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008565 if (paste != null) {
8566 if (!didFirst) {
8567 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008568 min = TextUtils.unpackRangeStartFromLong(minMax);
8569 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008570 Selection.setSelection((Spannable) mText, max);
8571 ((Editable) mText).replace(min, max, paste);
8572 didFirst = true;
8573 } else {
8574 ((Editable) mText).insert(getSelectionEnd(), "\n");
8575 ((Editable) mText).insert(getSelectionEnd(), paste);
8576 }
8577 }
8578 }
8579 stopSelectionActionMode();
8580 LAST_CUT_OR_COPY_TIME = 0;
8581 }
8582 }
8583
8584 private void setPrimaryClip(ClipData clip) {
8585 ClipboardManager clipboard = (ClipboardManager) getContext().
8586 getSystemService(Context.CLIPBOARD_SERVICE);
8587 clipboard.setPrimaryClip(clip);
8588 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8589 }
8590
Gilles Debunne60e21862012-01-30 15:04:14 -08008591 /**
8592 * Get the character offset closest to the specified absolute position. A typical use case is to
8593 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8594 *
8595 * @param x The horizontal absolute position of a point on screen
8596 * @param y The vertical absolute position of a point on screen
8597 * @return the character offset for the character whose position is closest to the specified
8598 * position. Returns -1 if there is no layout.
8599 */
8600 public int getOffsetForPosition(float x, float y) {
8601 if (getLayout() == null) return -1;
8602 final int line = getLineAtCoordinate(y);
8603 final int offset = getOffsetAtCoordinate(line, x);
8604 return offset;
8605 }
8606
Gilles Debunned88876a2012-03-16 17:34:04 -07008607 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008608 x -= getTotalPaddingLeft();
8609 // Clamp the position to inside of the view.
8610 x = Math.max(0.0f, x);
8611 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8612 x += getScrollX();
8613 return x;
8614 }
8615
Gilles Debunned88876a2012-03-16 17:34:04 -07008616 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008617 y -= getTotalPaddingTop();
8618 // Clamp the position to inside of the view.
8619 y = Math.max(0.0f, y);
8620 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8621 y += getScrollY();
8622 return getLayout().getLineForVertical((int) y);
8623 }
8624
8625 private int getOffsetAtCoordinate(int line, float x) {
8626 x = convertToLocalHorizontalCoordinate(x);
8627 return getLayout().getOffsetForHorizontal(line, x);
8628 }
8629
Gilles Debunne60e21862012-01-30 15:04:14 -08008630 @Override
8631 public boolean onDragEvent(DragEvent event) {
8632 switch (event.getAction()) {
8633 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008634 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008635
8636 case DragEvent.ACTION_DRAG_ENTERED:
8637 TextView.this.requestFocus();
8638 return true;
8639
8640 case DragEvent.ACTION_DRAG_LOCATION:
8641 final int offset = getOffsetForPosition(event.getX(), event.getY());
8642 Selection.setSelection((Spannable)mText, offset);
8643 return true;
8644
8645 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008646 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008647 return true;
8648
8649 case DragEvent.ACTION_DRAG_ENDED:
8650 case DragEvent.ACTION_DRAG_EXITED:
8651 default:
8652 return true;
8653 }
8654 }
8655
Gilles Debunne60e21862012-01-30 15:04:14 -08008656 boolean isInBatchEditMode() {
8657 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008658 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008659 if (ims != null) {
8660 return ims.mBatchEditNesting > 0;
8661 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008662 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008663 }
8664
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07008665 @Override
8666 public void onRtlPropertiesChanged(int layoutDirection) {
8667 super.onRtlPropertiesChanged(layoutDirection);
8668
8669 mTextDir = getTextDirectionHeuristic();
8670 }
8671
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008672 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008673 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008674 // passwords fields should be LTR
8675 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008676 }
8677
8678 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008679 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008680
8681 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008682 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008683 default:
8684 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008685 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008686 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008687 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008688 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008689 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008690 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008691 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008692 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008693 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008694 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008695 }
8696 }
8697
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008698 /**
8699 * @hide
8700 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008701 @Override
8702 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008703 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008704 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008705 return;
8706 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008707 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008708
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008709 // Resolve drawables
8710 if (mDrawables != null) {
8711 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008712 }
8713 }
8714
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008715 /**
8716 * @hide
8717 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008718 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008719 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008720 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008721 }
8722
8723 /**
8724 * @hide
8725 */
8726 protected void viewClicked(InputMethodManager imm) {
8727 if (imm != null) {
8728 imm.viewClicked(this);
8729 }
8730 }
8731
8732 /**
8733 * Deletes the range of text [start, end[.
8734 * @hide
8735 */
8736 protected void deleteText_internal(int start, int end) {
8737 ((Editable) mText).delete(start, end);
8738 }
8739
8740 /**
8741 * Replaces the range of text [start, end[ by replacement text
8742 * @hide
8743 */
8744 protected void replaceText_internal(int start, int end, CharSequence text) {
8745 ((Editable) mText).replace(start, end, text);
8746 }
8747
8748 /**
8749 * Sets a span on the specified range of text
8750 * @hide
8751 */
8752 protected void setSpan_internal(Object span, int start, int end, int flags) {
8753 ((Editable) mText).setSpan(span, start, end, flags);
8754 }
8755
8756 /**
8757 * Moves the cursor to the specified offset position in text
8758 * @hide
8759 */
8760 protected void setCursorPosition_internal(int start, int end) {
8761 Selection.setSelection(((Editable) mText), start, end);
8762 }
8763
8764 /**
8765 * An Editor should be created as soon as any of the editable-specific fields (grouped
8766 * inside the Editor object) is assigned to a non-default value.
8767 * This method will create the Editor if needed.
8768 *
8769 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8770 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8771 * Editor for backward compatibility, as soon as one of these fields is assigned.
8772 *
8773 * Also note that for performance reasons, the mEditor is created when needed, but not
8774 * reset when no more edit-specific fields are needed.
8775 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008776 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008777 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008778 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008779 }
8780 }
8781
Gilles Debunne60e21862012-01-30 15:04:14 -08008782 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008783 * @hide
8784 */
8785 @Override
8786 public CharSequence getIterableTextForAccessibility() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008787 if (!(mText instanceof Spannable)) {
8788 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008789 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008790 return mText;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008791 }
8792
8793 /**
8794 * @hide
8795 */
8796 @Override
8797 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8798 switch (granularity) {
8799 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8800 Spannable text = (Spannable) getIterableTextForAccessibility();
8801 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8802 AccessibilityIterators.LineTextSegmentIterator iterator =
8803 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8804 iterator.initialize(text, getLayout());
8805 return iterator;
8806 }
8807 } break;
8808 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8809 Spannable text = (Spannable) getIterableTextForAccessibility();
8810 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8811 AccessibilityIterators.PageTextSegmentIterator iterator =
8812 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8813 iterator.initialize(this);
8814 return iterator;
8815 }
8816 } break;
8817 }
8818 return super.getIteratorForGranularity(granularity);
8819 }
8820
8821 /**
8822 * @hide
8823 */
8824 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008825 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008826 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -08008827 }
8828
8829 /**
8830 * @hide
8831 */
8832 public boolean isAccessibilitySelectionExtendable() {
8833 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008834 }
8835
8836 /**
8837 * @hide
8838 */
8839 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008840 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008841 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -08008842 }
8843
8844 /**
8845 * @hide
8846 */
8847 @Override
8848 public void setAccessibilitySelection(int start, int end) {
8849 if (getAccessibilitySelectionStart() == start
8850 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008851 return;
8852 }
Svetoslavabad55d2013-05-07 18:49:51 -07008853 // Hide all selection controllers used for adjusting selection
8854 // since we are doing so explicitlty by other means and these
8855 // controllers interact with how selection behaves.
8856 if (mEditor != null) {
8857 mEditor.hideControllers();
8858 }
Svetoslav7c512842013-01-30 23:02:08 -08008859 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -07008860 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -08008861 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008862 } else {
Svetoslav7c512842013-01-30 23:02:08 -08008863 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008864 }
8865 }
8866
8867 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008868 * User interface state that is stored by TextView for implementing
8869 * {@link View#onSaveInstanceState}.
8870 */
8871 public static class SavedState extends BaseSavedState {
8872 int selStart;
8873 int selEnd;
8874 CharSequence text;
8875 boolean frozenWithFocus;
8876 CharSequence error;
8877
8878 SavedState(Parcelable superState) {
8879 super(superState);
8880 }
8881
8882 @Override
8883 public void writeToParcel(Parcel out, int flags) {
8884 super.writeToParcel(out, flags);
8885 out.writeInt(selStart);
8886 out.writeInt(selEnd);
8887 out.writeInt(frozenWithFocus ? 1 : 0);
8888 TextUtils.writeToParcel(text, out, flags);
8889
8890 if (error == null) {
8891 out.writeInt(0);
8892 } else {
8893 out.writeInt(1);
8894 TextUtils.writeToParcel(error, out, flags);
8895 }
8896 }
8897
8898 @Override
8899 public String toString() {
8900 String str = "TextView.SavedState{"
8901 + Integer.toHexString(System.identityHashCode(this))
8902 + " start=" + selStart + " end=" + selEnd;
8903 if (text != null) {
8904 str += " text=" + text;
8905 }
8906 return str + "}";
8907 }
8908
8909 @SuppressWarnings("hiding")
8910 public static final Parcelable.Creator<SavedState> CREATOR
8911 = new Parcelable.Creator<SavedState>() {
8912 public SavedState createFromParcel(Parcel in) {
8913 return new SavedState(in);
8914 }
8915
8916 public SavedState[] newArray(int size) {
8917 return new SavedState[size];
8918 }
8919 };
8920
8921 private SavedState(Parcel in) {
8922 super(in);
8923 selStart = in.readInt();
8924 selEnd = in.readInt();
8925 frozenWithFocus = (in.readInt() != 0);
8926 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8927
8928 if (in.readInt() != 0) {
8929 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8930 }
8931 }
8932 }
8933
8934 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8935 private char[] mChars;
8936 private int mStart, mLength;
8937
8938 public CharWrapper(char[] chars, int start, int len) {
8939 mChars = chars;
8940 mStart = start;
8941 mLength = len;
8942 }
8943
8944 /* package */ void set(char[] chars, int start, int len) {
8945 mChars = chars;
8946 mStart = start;
8947 mLength = len;
8948 }
8949
8950 public int length() {
8951 return mLength;
8952 }
8953
8954 public char charAt(int off) {
8955 return mChars[off + mStart];
8956 }
8957
8958 @Override
8959 public String toString() {
8960 return new String(mChars, mStart, mLength);
8961 }
8962
8963 public CharSequence subSequence(int start, int end) {
8964 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8965 throw new IndexOutOfBoundsException(start + ", " + end);
8966 }
8967
8968 return new String(mChars, start + mStart, end - start);
8969 }
8970
8971 public void getChars(int start, int end, char[] buf, int off) {
8972 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8973 throw new IndexOutOfBoundsException(start + ", " + end);
8974 }
8975
8976 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8977 }
8978
8979 public void drawText(Canvas c, int start, int end,
8980 float x, float y, Paint p) {
8981 c.drawText(mChars, start + mStart, end - start, x, y, p);
8982 }
8983
8984 public void drawTextRun(Canvas c, int start, int end,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008985 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008986 int count = end - start;
8987 int contextCount = contextEnd - contextStart;
8988 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008989 contextCount, x, y, flags, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08008990 }
8991
8992 public float measureText(int start, int end, Paint p) {
8993 return p.measureText(mChars, start + mStart, end - start);
8994 }
8995
8996 public int getTextWidths(int start, int end, float[] widths, Paint p) {
8997 return p.getTextWidths(mChars, start + mStart, end - start, widths);
8998 }
8999
9000 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009001 int contextEnd, int flags, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08009002 Paint p) {
9003 int count = end - start;
9004 int contextCount = contextEnd - contextStart;
9005 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009006 contextStart + mStart, contextCount, flags, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08009007 advancesIndex);
9008 }
9009
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009010 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
Gilles Debunne60e21862012-01-30 15:04:14 -08009011 int offset, int cursorOpt, Paint p) {
9012 int contextCount = contextEnd - contextStart;
9013 return p.getTextRunCursor(mChars, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07009014 contextCount, flags, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08009015 }
9016 }
9017
Gilles Debunne60e21862012-01-30 15:04:14 -08009018 private static final class Marquee extends Handler {
9019 // TODO: Add an option to configure this
9020 private static final float MARQUEE_DELTA_MAX = 0.07f;
9021 private static final int MARQUEE_DELAY = 1200;
9022 private static final int MARQUEE_RESTART_DELAY = 1200;
9023 private static final int MARQUEE_RESOLUTION = 1000 / 30;
9024 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9025
9026 private static final byte MARQUEE_STOPPED = 0x0;
9027 private static final byte MARQUEE_STARTING = 0x1;
9028 private static final byte MARQUEE_RUNNING = 0x2;
9029
9030 private static final int MESSAGE_START = 0x1;
9031 private static final int MESSAGE_TICK = 0x2;
9032 private static final int MESSAGE_RESTART = 0x3;
9033
9034 private final WeakReference<TextView> mView;
9035
9036 private byte mStatus = MARQUEE_STOPPED;
9037 private final float mScrollUnit;
9038 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009039 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009040 private float mGhostStart;
9041 private float mGhostOffset;
9042 private float mFadeStop;
9043 private int mRepeatLimit;
9044
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009045 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009046
9047 Marquee(TextView v) {
9048 final float density = v.getContext().getResources().getDisplayMetrics().density;
9049 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9050 mView = new WeakReference<TextView>(v);
9051 }
9052
9053 @Override
9054 public void handleMessage(Message msg) {
9055 switch (msg.what) {
9056 case MESSAGE_START:
9057 mStatus = MARQUEE_RUNNING;
9058 tick();
9059 break;
9060 case MESSAGE_TICK:
9061 tick();
9062 break;
9063 case MESSAGE_RESTART:
9064 if (mStatus == MARQUEE_RUNNING) {
9065 if (mRepeatLimit >= 0) {
9066 mRepeatLimit--;
9067 }
9068 start(mRepeatLimit);
9069 }
9070 break;
9071 }
9072 }
9073
9074 void tick() {
9075 if (mStatus != MARQUEE_RUNNING) {
9076 return;
9077 }
9078
9079 removeMessages(MESSAGE_TICK);
9080
9081 final TextView textView = mView.get();
9082 if (textView != null && (textView.isFocused() || textView.isSelected())) {
9083 mScroll += mScrollUnit;
9084 if (mScroll > mMaxScroll) {
9085 mScroll = mMaxScroll;
9086 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9087 } else {
9088 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9089 }
9090 textView.invalidate();
9091 }
9092 }
9093
9094 void stop() {
9095 mStatus = MARQUEE_STOPPED;
9096 removeMessages(MESSAGE_START);
9097 removeMessages(MESSAGE_RESTART);
9098 removeMessages(MESSAGE_TICK);
9099 resetScroll();
9100 }
9101
9102 private void resetScroll() {
9103 mScroll = 0.0f;
9104 final TextView textView = mView.get();
9105 if (textView != null) textView.invalidate();
9106 }
9107
9108 void start(int repeatLimit) {
9109 if (repeatLimit == 0) {
9110 stop();
9111 return;
9112 }
9113 mRepeatLimit = repeatLimit;
9114 final TextView textView = mView.get();
9115 if (textView != null && textView.mLayout != null) {
9116 mStatus = MARQUEE_STARTING;
9117 mScroll = 0.0f;
9118 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9119 textView.getCompoundPaddingRight();
9120 final float lineWidth = textView.mLayout.getLineWidth(0);
9121 final float gap = textWidth / 3.0f;
9122 mGhostStart = lineWidth - textWidth + gap;
9123 mMaxScroll = mGhostStart + textWidth;
9124 mGhostOffset = lineWidth + gap;
9125 mFadeStop = lineWidth + textWidth / 6.0f;
9126 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9127
9128 textView.invalidate();
9129 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9130 }
9131 }
9132
9133 float getGhostOffset() {
9134 return mGhostOffset;
9135 }
9136
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009137 float getScroll() {
9138 return mScroll;
9139 }
9140
9141 float getMaxFadeScroll() {
9142 return mMaxFadeScroll;
9143 }
9144
Gilles Debunne60e21862012-01-30 15:04:14 -08009145 boolean shouldDrawLeftFade() {
9146 return mScroll <= mFadeStop;
9147 }
9148
9149 boolean shouldDrawGhost() {
9150 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9151 }
9152
9153 boolean isRunning() {
9154 return mStatus == MARQUEE_RUNNING;
9155 }
9156
9157 boolean isStopped() {
9158 return mStatus == MARQUEE_STOPPED;
9159 }
9160 }
9161
Gilles Debunne60e21862012-01-30 15:04:14 -08009162 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9163
9164 private CharSequence mBeforeText;
9165
Gilles Debunne60e21862012-01-30 15:04:14 -08009166 public void beforeTextChanged(CharSequence buffer, int start,
9167 int before, int after) {
9168 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9169 + " before=" + before + " after=" + after + ": " + buffer);
9170
9171 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08009172 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9173 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009174 mBeforeText = buffer.toString();
9175 }
9176
9177 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9178 }
9179
Gilles Debunned88876a2012-03-16 17:34:04 -07009180 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009181 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9182 + " before=" + before + " after=" + after + ": " + buffer);
9183 TextView.this.handleTextChanged(buffer, start, before, after);
9184
Gilles Debunne60e21862012-01-30 15:04:14 -08009185 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9186 (isFocused() || isSelected() && isShown())) {
9187 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9188 mBeforeText = null;
9189 }
9190 }
9191
9192 public void afterTextChanged(Editable buffer) {
9193 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9194 TextView.this.sendAfterTextChanged(buffer);
9195
9196 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9197 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9198 }
9199 }
9200
Gilles Debunned88876a2012-03-16 17:34:04 -07009201 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009202 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9203 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9204 TextView.this.spanChange(buf, what, s, st, e, en);
9205 }
9206
9207 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9208 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9209 + " what=" + what + ": " + buf);
9210 TextView.this.spanChange(buf, what, -1, s, -1, e);
9211 }
9212
9213 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9214 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9215 + " what=" + what + ": " + buf);
9216 TextView.this.spanChange(buf, what, s, -1, e, -1);
9217 }
satoka67a3cf2011-09-07 17:14:03 +09009218 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009219}