blob: 816bb18dcd56071fdf010a5ace3361b395a0dea6 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.text.Spanned;
60import android.text.SpannedString;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061import android.text.StaticLayout;
Doug Feltcb3791202011-07-07 11:57:48 -070062import android.text.TextDirectionHeuristic;
63import android.text.TextDirectionHeuristics;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064import android.text.TextPaint;
65import android.text.TextUtils;
Adam Powell282e3772011-08-30 16:51:11 -070066import android.text.TextUtils.TruncateAt;
Gilles Debunne0eea6682011-08-29 13:30:31 -070067import android.text.TextWatcher;
Adam Powell7f8f79a2011-07-07 18:35:54 -070068import android.text.method.AllCapsTransformationMethod;
Gilles Debunne86b9c782010-11-11 10:43:48 -080069import android.text.method.ArrowKeyMovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import android.text.method.DateKeyListener;
71import android.text.method.DateTimeKeyListener;
72import android.text.method.DialerKeyListener;
73import android.text.method.DigitsKeyListener;
74import android.text.method.KeyListener;
75import android.text.method.LinkMovementMethod;
76import android.text.method.MetaKeyKeyListener;
77import android.text.method.MovementMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078import android.text.method.PasswordTransformationMethod;
79import android.text.method.SingleLineTransformationMethod;
80import android.text.method.TextKeyListener;
svetoslavganov75986cf2009-05-14 22:28:01 -070081import android.text.method.TimeKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082import android.text.method.TransformationMethod;
Adam Powell7f8f79a2011-07-07 18:35:54 -070083import android.text.method.TransformationMethod2;
Gilles Debunne214a8622011-04-26 15:44:37 -070084import android.text.method.WordIterator;
Gilles Debunneb35ab7b2011-12-05 15:54:00 -080085import android.text.style.CharacterStyle;
Gilles Debunnef3895ed2010-12-21 12:53:58 -080086import android.text.style.ClickableSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087import android.text.style.ParagraphStyle;
Gilles Debunne6435a562011-08-04 21:22:30 -070088import android.text.style.SpellCheckSpan;
Gilles Debunne2037b822011-04-22 13:07:33 -070089import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090import android.text.style.URLSpan;
91import android.text.style.UpdateAppearance;
92import android.text.util.Linkify;
93import android.util.AttributeSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094import android.util.FloatMath;
svetoslavganov75986cf2009-05-14 22:28:01 -070095import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096import android.util.TypedValue;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -070097import android.view.AccessibilityIterators.TextSegmentIterator;
Gilles Debunne27113f82010-08-23 12:09:14 -070098import android.view.ActionMode;
Gilles Debunnef170a342010-11-11 11:08:59 -080099import android.view.DragEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800100import android.view.Gravity;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700101import android.view.HapticFeedbackConstants;
Jeff Brown6b53e8d2010-11-10 16:03:06 -0800102import android.view.KeyCharacterMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103import android.view.KeyEvent;
Gilles Debunnef788a9f2010-07-22 10:17:23 -0700104import android.view.Menu;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105import android.view.MenuItem;
106import android.view.MotionEvent;
107import android.view.View;
Gilles Debunne65f60412010-10-15 16:18:35 -0700108import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109import android.view.ViewDebug;
Gilles Debunne27113f82010-08-23 12:09:14 -0700110import android.view.ViewGroup.LayoutParams;
Gilles Debunne3784a7f2011-07-15 13:49:38 -0700111import android.view.ViewRootImpl;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112import android.view.ViewTreeObserver;
svetoslavganov75986cf2009-05-14 22:28:01 -0700113import android.view.accessibility.AccessibilityEvent;
114import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700115import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116import android.view.animation.AnimationUtils;
117import android.view.inputmethod.BaseInputConnection;
118import android.view.inputmethod.CompletionInfo;
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -0800119import android.view.inputmethod.CorrectionInfo;
svetoslavganov75986cf2009-05-14 22:28:01 -0700120import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121import android.view.inputmethod.ExtractedText;
122import android.view.inputmethod.ExtractedTextRequest;
123import android.view.inputmethod.InputConnection;
124import android.view.inputmethod.InputMethodManager;
satok05f24702011-11-02 19:29:35 +0900125import android.view.textservice.SpellCheckerSubtype;
126import android.view.textservice.TextServicesManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127import android.widget.RemoteViews.RemoteView;
128
Gilles Debunne22378292011-08-12 10:38:52 -0700129import com.android.internal.util.FastMath;
130import com.android.internal.widget.EditableInputConnection;
131
132import org.xmlpull.v1.XmlPullParserException;
133
Gilles Debunne27113f82010-08-23 12:09:14 -0700134import java.io.IOException;
135import java.lang.ref.WeakReference;
136import java.util.ArrayList;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -0700137import java.util.Locale;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900138import java.util.concurrent.locks.ReentrantLock;
Gilles Debunne27113f82010-08-23 12:09:14 -0700139
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700140import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
141
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142/**
143 * Displays text to the user and optionally allows them to edit it. A TextView
144 * is a complete text editor, however the basic class is configured to not
145 * allow editing; see {@link EditText} for a subclass that configures the text
146 * view for editing.
147 *
148 * <p>
Joe Malin10d96952013-05-29 17:49:09 -0700149 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
150 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
151 * android:textIsSelectable} to "true" or call
152 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
153 * allows users to make selection gestures in the TextView, which in turn triggers the system's
154 * built-in copy/paste controls.
155 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 * <b>XML attributes</b>
157 * <p>
158 * See {@link android.R.styleable#TextView TextView Attributes},
159 * {@link android.R.styleable#View View Attributes}
160 *
161 * @attr ref android.R.styleable#TextView_text
162 * @attr ref android.R.styleable#TextView_bufferType
163 * @attr ref android.R.styleable#TextView_hint
164 * @attr ref android.R.styleable#TextView_textColor
165 * @attr ref android.R.styleable#TextView_textColorHighlight
166 * @attr ref android.R.styleable#TextView_textColorHint
Romain Guyd6a463a2009-05-21 23:10:10 -0700167 * @attr ref android.R.styleable#TextView_textAppearance
168 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 * @attr ref android.R.styleable#TextView_textSize
170 * @attr ref android.R.styleable#TextView_textScaleX
Raph Leviend570e892012-05-09 11:45:34 -0700171 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 * @attr ref android.R.styleable#TextView_typeface
173 * @attr ref android.R.styleable#TextView_textStyle
174 * @attr ref android.R.styleable#TextView_cursorVisible
175 * @attr ref android.R.styleable#TextView_maxLines
176 * @attr ref android.R.styleable#TextView_maxHeight
177 * @attr ref android.R.styleable#TextView_lines
178 * @attr ref android.R.styleable#TextView_height
179 * @attr ref android.R.styleable#TextView_minLines
180 * @attr ref android.R.styleable#TextView_minHeight
181 * @attr ref android.R.styleable#TextView_maxEms
182 * @attr ref android.R.styleable#TextView_maxWidth
183 * @attr ref android.R.styleable#TextView_ems
184 * @attr ref android.R.styleable#TextView_width
185 * @attr ref android.R.styleable#TextView_minEms
186 * @attr ref android.R.styleable#TextView_minWidth
187 * @attr ref android.R.styleable#TextView_gravity
188 * @attr ref android.R.styleable#TextView_scrollHorizontally
189 * @attr ref android.R.styleable#TextView_password
190 * @attr ref android.R.styleable#TextView_singleLine
191 * @attr ref android.R.styleable#TextView_selectAllOnFocus
192 * @attr ref android.R.styleable#TextView_includeFontPadding
193 * @attr ref android.R.styleable#TextView_maxLength
194 * @attr ref android.R.styleable#TextView_shadowColor
195 * @attr ref android.R.styleable#TextView_shadowDx
196 * @attr ref android.R.styleable#TextView_shadowDy
197 * @attr ref android.R.styleable#TextView_shadowRadius
198 * @attr ref android.R.styleable#TextView_autoLink
199 * @attr ref android.R.styleable#TextView_linksClickable
200 * @attr ref android.R.styleable#TextView_numeric
201 * @attr ref android.R.styleable#TextView_digits
202 * @attr ref android.R.styleable#TextView_phoneNumber
203 * @attr ref android.R.styleable#TextView_inputMethod
204 * @attr ref android.R.styleable#TextView_capitalize
205 * @attr ref android.R.styleable#TextView_autoText
206 * @attr ref android.R.styleable#TextView_editable
Romain Guyd6a463a2009-05-21 23:10:10 -0700207 * @attr ref android.R.styleable#TextView_freezesText
208 * @attr ref android.R.styleable#TextView_ellipsize
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 * @attr ref android.R.styleable#TextView_drawableTop
210 * @attr ref android.R.styleable#TextView_drawableBottom
211 * @attr ref android.R.styleable#TextView_drawableRight
212 * @attr ref android.R.styleable#TextView_drawableLeft
Fabrice Di Megliod1591092012-03-07 15:34:38 -0800213 * @attr ref android.R.styleable#TextView_drawableStart
214 * @attr ref android.R.styleable#TextView_drawableEnd
Romain Guyd6a463a2009-05-21 23:10:10 -0700215 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 * @attr ref android.R.styleable#TextView_lineSpacingExtra
217 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
218 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
Romain Guyd6a463a2009-05-21 23:10:10 -0700219 * @attr ref android.R.styleable#TextView_inputType
220 * @attr ref android.R.styleable#TextView_imeOptions
221 * @attr ref android.R.styleable#TextView_privateImeOptions
222 * @attr ref android.R.styleable#TextView_imeActionLabel
223 * @attr ref android.R.styleable#TextView_imeActionId
224 * @attr ref android.R.styleable#TextView_editorExtras
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 */
226@RemoteView
227public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -0700228 static final String LOG_TAG = "TextView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 static final boolean DEBUG_EXTRACT = false;
Gilles Debunneb7012e842011-02-24 15:40:38 -0800230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 // Enum for the "typeface" XML parameter.
232 // TODO: How can we get this from the XML instead of hardcoding it here?
233 private static final int SANS = 1;
234 private static final int SERIF = 2;
235 private static final int MONOSPACE = 3;
236
237 // Bitfield for the "numeric" XML parameter.
238 // TODO: How can we get this from the XML instead of hardcoding it here?
239 private static final int SIGNED = 2;
240 private static final int DECIMAL = 4;
241
Adam Powell282e3772011-08-30 16:51:11 -0700242 /**
243 * Draw marquee text with fading edges as usual
244 */
245 private static final int MARQUEE_FADE_NORMAL = 0;
246
247 /**
248 * Draw marquee text as ellipsize end while inactive instead of with the fade.
249 * (Useful for devices where the fade can be expensive if overdone)
250 */
251 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
252
253 /**
254 * Draw marquee text with fading edges because it is currently active/animating.
255 */
256 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
257
Gilles Debunne60e21862012-01-30 15:04:14 -0800258 private static final int LINES = 1;
259 private static final int EMS = LINES;
260 private static final int PIXELS = 2;
261
262 private static final RectF TEMP_RECTF = new RectF();
Gilles Debunne60e21862012-01-30 15:04:14 -0800263
264 // XXX should be much larger
265 private static final int VERY_WIDE = 1024*1024;
Gilles Debunne60e21862012-01-30 15:04:14 -0800266 private static final int ANIMATED_SCROLL_GAP = 250;
267
268 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
269 private static final Spanned EMPTY_SPANNED = new SpannedString("");
270
Gilles Debunne60e21862012-01-30 15:04:14 -0800271 private static final int CHANGE_WATCHER_PRIORITY = 100;
272
273 // New state used to change background based on whether this TextView is multiline.
274 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
275
276 // System wide time for last cut or copy action.
Gilles Debunned88876a2012-03-16 17:34:04 -0700277 static long LAST_CUT_OR_COPY_TIME;
Gilles Debunne60e21862012-01-30 15:04:14 -0800278
Gilles Debunne60e21862012-01-30 15:04:14 -0800279 private ColorStateList mTextColor;
280 private ColorStateList mHintTextColor;
281 private ColorStateList mLinkTextColor;
282 private int mCurTextColor;
283 private int mCurHintTextColor;
284 private boolean mFreezesText;
285 private boolean mTemporaryDetach;
286 private boolean mDispatchTemporaryDetach;
287
288 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
289 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
290
291 private float mShadowRadius, mShadowDx, mShadowDy;
292
293 private boolean mPreDrawRegistered;
294
Michael Wright3a7e4832013-02-11 15:55:50 -0800295 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
296 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
297 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
298 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
299 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
300 private boolean mPreventDefaultMovement;
301
Gilles Debunne60e21862012-01-30 15:04:14 -0800302 private TextUtils.TruncateAt mEllipsize;
303
304 static class Drawables {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800305 final static int DRAWABLE_NONE = -1;
306 final static int DRAWABLE_RIGHT = 0;
307 final static int DRAWABLE_LEFT = 1;
308
Gilles Debunne60e21862012-01-30 15:04:14 -0800309 final Rect mCompoundRect = new Rect();
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800310
Gilles Debunne60e21862012-01-30 15:04:14 -0800311 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800312 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
313
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700314 Drawable mDrawableLeftInitial, mDrawableRightInitial;
315 boolean mIsRtlCompatibilityMode;
316 boolean mOverride;
317
Gilles Debunne60e21862012-01-30 15:04:14 -0800318 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800319 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
320
Gilles Debunne60e21862012-01-30 15:04:14 -0800321 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800322 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
323
Gilles Debunne60e21862012-01-30 15:04:14 -0800324 int mDrawablePadding;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800325
326 int mDrawableSaved = DRAWABLE_NONE;
327
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700328 public Drawables(Context context) {
329 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
330 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
331 !context.getApplicationInfo().hasRtlSupport());
332 mOverride = false;
333 }
334
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800335 public void resolveWithLayoutDirection(int layoutDirection) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700336 // First reset "left" and "right" drawables to their initial values
337 mDrawableLeft = mDrawableLeftInitial;
338 mDrawableRight = mDrawableRightInitial;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800339
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700340 if (mIsRtlCompatibilityMode) {
341 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
342 if (mDrawableStart != null && mDrawableLeft == null) {
343 mDrawableLeft = mDrawableStart;
344 mDrawableSizeLeft = mDrawableSizeStart;
345 mDrawableHeightLeft = mDrawableHeightStart;
346 }
347 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
348 if (mDrawableEnd != null && mDrawableRight == null) {
349 mDrawableRight = mDrawableEnd;
350 mDrawableSizeRight = mDrawableSizeEnd;
351 mDrawableHeightRight = mDrawableHeightEnd;
352 }
353 } else {
354 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
355 // drawable if and only if they have been defined
356 switch(layoutDirection) {
357 case LAYOUT_DIRECTION_RTL:
358 if (mOverride) {
359 mDrawableRight = mDrawableStart;
360 mDrawableSizeRight = mDrawableSizeStart;
361 mDrawableHeightRight = mDrawableHeightStart;
362 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800363
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700364 if (mOverride) {
365 mDrawableLeft = mDrawableEnd;
366 mDrawableSizeLeft = mDrawableSizeEnd;
367 mDrawableHeightLeft = mDrawableHeightEnd;
368 }
369 break;
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800370
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700371 case LAYOUT_DIRECTION_LTR:
372 default:
373 if (mOverride) {
374 mDrawableLeft = mDrawableStart;
375 mDrawableSizeLeft = mDrawableSizeStart;
376 mDrawableHeightLeft = mDrawableHeightStart;
377 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800378
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -0700379 if (mOverride) {
380 mDrawableRight = mDrawableEnd;
381 mDrawableSizeRight = mDrawableSizeEnd;
382 mDrawableHeightRight = mDrawableHeightEnd;
383 }
384 break;
385 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800386 }
387 applyErrorDrawableIfNeeded(layoutDirection);
388 updateDrawablesLayoutDirection(layoutDirection);
389 }
390
391 private void updateDrawablesLayoutDirection(int layoutDirection) {
392 if (mDrawableLeft != null) {
393 mDrawableLeft.setLayoutDirection(layoutDirection);
394 }
395 if (mDrawableRight != null) {
396 mDrawableRight.setLayoutDirection(layoutDirection);
397 }
398 if (mDrawableTop != null) {
399 mDrawableTop.setLayoutDirection(layoutDirection);
400 }
401 if (mDrawableBottom != null) {
402 mDrawableBottom.setLayoutDirection(layoutDirection);
403 }
404 }
405
406 public void setErrorDrawable(Drawable dr, TextView tv) {
407 if (mDrawableError != dr && mDrawableError != null) {
408 mDrawableError.setCallback(null);
409 }
410 mDrawableError = dr;
411
412 final Rect compoundRect = mCompoundRect;
413 int[] state = tv.getDrawableState();
414
415 if (mDrawableError != null) {
416 mDrawableError.setState(state);
417 mDrawableError.copyBounds(compoundRect);
418 mDrawableError.setCallback(tv);
419 mDrawableSizeError = compoundRect.width();
420 mDrawableHeightError = compoundRect.height();
421 } else {
422 mDrawableSizeError = mDrawableHeightError = 0;
423 }
424 }
425
426 private void applyErrorDrawableIfNeeded(int layoutDirection) {
427 // first restore the initial state if needed
428 switch (mDrawableSaved) {
429 case DRAWABLE_LEFT:
430 mDrawableLeft = mDrawableTemp;
431 mDrawableSizeLeft = mDrawableSizeTemp;
432 mDrawableHeightLeft = mDrawableHeightTemp;
433 break;
434 case DRAWABLE_RIGHT:
435 mDrawableRight = mDrawableTemp;
436 mDrawableSizeRight = mDrawableSizeTemp;
437 mDrawableHeightRight = mDrawableHeightTemp;
438 break;
439 case DRAWABLE_NONE:
440 default:
441 }
442 // then, if needed, assign the Error drawable to the correct location
443 if (mDrawableError != null) {
444 switch(layoutDirection) {
445 case LAYOUT_DIRECTION_RTL:
446 mDrawableSaved = DRAWABLE_LEFT;
447
448 mDrawableTemp = mDrawableLeft;
449 mDrawableSizeTemp = mDrawableSizeLeft;
450 mDrawableHeightTemp = mDrawableHeightLeft;
451
452 mDrawableLeft = mDrawableError;
453 mDrawableSizeLeft = mDrawableSizeError;
454 mDrawableHeightLeft = mDrawableHeightError;
455 break;
456 case LAYOUT_DIRECTION_LTR:
457 default:
458 mDrawableSaved = DRAWABLE_RIGHT;
459
460 mDrawableTemp = mDrawableRight;
461 mDrawableSizeTemp = mDrawableSizeRight;
462 mDrawableHeightTemp = mDrawableHeightRight;
463
464 mDrawableRight = mDrawableError;
465 mDrawableSizeRight = mDrawableSizeError;
466 mDrawableHeightRight = mDrawableHeightError;
467 break;
468 }
469 }
470 }
Gilles Debunne60e21862012-01-30 15:04:14 -0800471 }
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -0800472
Gilles Debunned88876a2012-03-16 17:34:04 -0700473 Drawables mDrawables;
Gilles Debunne60e21862012-01-30 15:04:14 -0800474
475 private CharWrapper mCharWrapper;
476
477 private Marquee mMarquee;
478 private boolean mRestartMarquee;
479
480 private int mMarqueeRepeatLimit = 3;
481
Fabrice Di Meglio1957d282012-10-25 17:42:39 -0700482 private int mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800483
484 /**
485 * On some devices the fading edges add a performance penalty if used
486 * extensively in the same layout. This mode indicates how the marquee
487 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
488 */
489 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
490
491 /**
492 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
493 * the layout that should be used when the mode switches.
494 */
495 private Layout mSavedMarqueeModeLayout;
496
497 @ViewDebug.ExportedProperty(category = "text")
498 private CharSequence mText;
499 private CharSequence mTransformed;
500 private BufferType mBufferType = BufferType.NORMAL;
501
502 private CharSequence mHint;
503 private Layout mHintLayout;
504
505 private MovementMethod mMovement;
506
507 private TransformationMethod mTransformation;
508 private boolean mAllowTransformationLengthChange;
509 private ChangeWatcher mChangeWatcher;
510
511 private ArrayList<TextWatcher> mListeners;
512
513 // display attributes
514 private final TextPaint mTextPaint;
515 private boolean mUserSetTextScaleX;
516 private Layout mLayout;
517
518 private int mGravity = Gravity.TOP | Gravity.START;
519 private boolean mHorizontallyScrolling;
520
521 private int mAutoLinkMask;
522 private boolean mLinksClickable = true;
523
524 private float mSpacingMult = 1.0f;
525 private float mSpacingAdd = 0.0f;
526
527 private int mMaximum = Integer.MAX_VALUE;
528 private int mMaxMode = LINES;
529 private int mMinimum = 0;
530 private int mMinMode = LINES;
531
532 private int mOldMaximum = mMaximum;
533 private int mOldMaxMode = mMaxMode;
534
535 private int mMaxWidth = Integer.MAX_VALUE;
536 private int mMaxWidthMode = PIXELS;
537 private int mMinWidth = 0;
538 private int mMinWidthMode = PIXELS;
539
540 private boolean mSingleLine;
541 private int mDesiredHeightAtMeasure = -1;
542 private boolean mIncludePad = true;
Raph Levienf5c1a872012-10-15 17:22:26 -0700543 private int mDeferScroll = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -0800544
545 // tmp primitives, so we don't alloc them on each draw
546 private Rect mTempRect;
547 private long mLastScroll;
548 private Scroller mScroller;
549
550 private BoringLayout.Metrics mBoring, mHintBoring;
551 private BoringLayout mSavedLayout, mSavedHintLayout;
552
553 private TextDirectionHeuristic mTextDir;
554
555 private InputFilter[] mFilters = NO_FILTERS;
556
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +0900557 private volatile Locale mCurrentSpellCheckerLocaleCache;
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +0900558
Gilles Debunne83051b82012-02-24 20:01:13 -0800559 // It is possible to have a selection even when mEditor is null (programmatically set, like when
560 // a link is pressed). These highlight-related fields do not go in mEditor.
Gilles Debunned88876a2012-03-16 17:34:04 -0700561 int mHighlightColor = 0x6633B5E5;
Gilles Debunne83051b82012-02-24 20:01:13 -0800562 private Path mHighlightPath;
563 private final Paint mHighlightPaint;
564 private boolean mHighlightPathBogus = true;
565
Gilles Debunne60e21862012-01-30 15:04:14 -0800566 // Although these fields are specific to editable text, they are not added to Editor because
567 // they are defined by the TextView's style and are theme-dependent.
Gilles Debunned88876a2012-03-16 17:34:04 -0700568 int mCursorDrawableRes;
Gilles Debunne60e21862012-01-30 15:04:14 -0800569 // These four fields, could be moved to Editor, since we know their default values and we
570 // could condition the creation of the Editor to a non standard value. This is however
571 // brittle since the hardcoded values here (such as
572 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
573 // default style is modified.
Gilles Debunned88876a2012-03-16 17:34:04 -0700574 int mTextSelectHandleLeftRes;
575 int mTextSelectHandleRightRes;
576 int mTextSelectHandleRes;
577 int mTextEditSuggestionItemLayout;
Gilles Debunne60e21862012-01-30 15:04:14 -0800578
579 /**
580 * EditText specific data, created on demand when one of the Editor fields is used.
Gilles Debunne5fae9962012-05-08 14:53:20 -0700581 * See {@link #createEditorIfNeeded()}.
Gilles Debunne60e21862012-01-30 15:04:14 -0800582 */
583 private Editor mEditor;
584
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800585 /*
586 * Kick-start the font cache for the zygote process (to pay the cost of
587 * initializing freetype for our default font only once).
588 */
589 static {
590 Paint p = new Paint();
591 p.setAntiAlias(true);
592 // We don't care about the result, just the side-effect of measuring.
593 p.measureText("H");
594 }
595
596 /**
597 * Interface definition for a callback to be invoked when an action is
598 * performed on the editor.
599 */
600 public interface OnEditorActionListener {
601 /**
602 * Called when an action is being performed.
603 *
604 * @param v The view that was clicked.
605 * @param actionId Identifier of the action. This will be either the
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700606 * identifier you supplied, or {@link EditorInfo#IME_NULL
607 * EditorInfo.IME_NULL} if being called due to the enter key
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800608 * being pressed.
609 * @param event If triggered by an enter key, this is the event;
610 * otherwise, this is null.
611 * @return Return true if you have consumed the action, else false.
612 */
613 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
614 }
Gilles Debunne21078e42011-08-02 10:22:35 -0700615
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 public TextView(Context context) {
617 this(context, null);
618 }
619
Gilles Debunnec1714022012-01-17 13:59:23 -0800620 public TextView(Context context, AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800621 this(context, attrs, com.android.internal.R.attr.textViewStyle);
622 }
623
Gilles Debunnee15b3582010-06-16 15:17:21 -0700624 @SuppressWarnings("deprecation")
Gilles Debunnec1714022012-01-17 13:59:23 -0800625 public TextView(Context context, AttributeSet attrs, int defStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 super(context, attrs, defStyle);
627 mText = "";
628
Christopher Tate1373a8e2011-11-10 19:59:13 -0800629 final Resources res = getResources();
630 final CompatibilityInfo compat = res.getCompatibilityInfo();
631
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800632 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Christopher Tate1373a8e2011-11-10 19:59:13 -0800633 mTextPaint.density = res.getDisplayMetrics().density;
634 mTextPaint.setCompatibilityScaling(compat.applicationScale);
Gilles Debunne8cbb4c62011-01-24 12:33:56 -0800635
Gilles Debunne83051b82012-02-24 20:01:13 -0800636 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
637 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
638
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 mMovement = getDefaultMovementMethod();
Gilles Debunne60e21862012-01-30 15:04:14 -0800640
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 mTransformation = null;
642
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643 int textColorHighlight = 0;
644 ColorStateList textColor = null;
645 ColorStateList textColorHint = null;
646 ColorStateList textColorLink = null;
647 int textSize = 15;
Raph Leviend570e892012-05-09 11:45:34 -0700648 String fontFamily = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800649 int typefaceIndex = -1;
650 int styleIndex = -1;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700651 boolean allCaps = false;
Adam Powellac91df82013-02-14 13:48:47 -0800652 int shadowcolor = 0;
653 float dx = 0, dy = 0, r = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700655 final Resources.Theme theme = context.getTheme();
656
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800657 /*
658 * Look the appearance up without checking first if it exists because
659 * almost every TextView has one and it greatly simplifies the logic
660 * to be able to parse the appearance first and then let specific tags
661 * for this View override it.
662 */
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700663 TypedArray a = theme.obtainStyledAttributes(
664 attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 TypedArray appearance = null;
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700666 int ap = a.getResourceId(
667 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
668 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800669 if (ap != -1) {
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700670 appearance = theme.obtainStyledAttributes(
671 ap, com.android.internal.R.styleable.TextAppearance);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800672 }
673 if (appearance != null) {
674 int n = appearance.getIndexCount();
675 for (int i = 0; i < n; i++) {
676 int attr = appearance.getIndex(i);
677
678 switch (attr) {
679 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
680 textColorHighlight = appearance.getColor(attr, textColorHighlight);
681 break;
682
683 case com.android.internal.R.styleable.TextAppearance_textColor:
684 textColor = appearance.getColorStateList(attr);
685 break;
686
687 case com.android.internal.R.styleable.TextAppearance_textColorHint:
688 textColorHint = appearance.getColorStateList(attr);
689 break;
690
691 case com.android.internal.R.styleable.TextAppearance_textColorLink:
692 textColorLink = appearance.getColorStateList(attr);
693 break;
694
695 case com.android.internal.R.styleable.TextAppearance_textSize:
696 textSize = appearance.getDimensionPixelSize(attr, textSize);
697 break;
698
699 case com.android.internal.R.styleable.TextAppearance_typeface:
700 typefaceIndex = appearance.getInt(attr, -1);
701 break;
702
Raph Leviend570e892012-05-09 11:45:34 -0700703 case com.android.internal.R.styleable.TextAppearance_fontFamily:
704 fontFamily = appearance.getString(attr);
705 break;
706
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800707 case com.android.internal.R.styleable.TextAppearance_textStyle:
708 styleIndex = appearance.getInt(attr, -1);
709 break;
Adam Powell7f8f79a2011-07-07 18:35:54 -0700710
711 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
712 allCaps = appearance.getBoolean(attr, false);
713 break;
Adam Powellac91df82013-02-14 13:48:47 -0800714
715 case com.android.internal.R.styleable.TextAppearance_shadowColor:
716 shadowcolor = a.getInt(attr, 0);
717 break;
718
719 case com.android.internal.R.styleable.TextAppearance_shadowDx:
720 dx = a.getFloat(attr, 0);
721 break;
722
723 case com.android.internal.R.styleable.TextAppearance_shadowDy:
724 dy = a.getFloat(attr, 0);
725 break;
726
727 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
728 r = a.getFloat(attr, 0);
729 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 }
731 }
732
733 appearance.recycle();
734 }
735
736 boolean editable = getDefaultEditable();
737 CharSequence inputMethod = null;
738 int numeric = 0;
739 CharSequence digits = null;
740 boolean phone = false;
741 boolean autotext = false;
742 int autocap = -1;
743 int buffertype = 0;
744 boolean selectallonfocus = false;
745 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700746 drawableBottom = null, drawableStart = null, drawableEnd = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800747 int drawablePadding = 0;
748 int ellipsize = -1;
Gilles Debunnef95449d2010-11-05 13:54:13 -0700749 boolean singleLine = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 int maxlength = -1;
751 CharSequence text = "";
Romain Guy4dc4f732009-06-19 15:16:40 -0700752 CharSequence hint = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800753 boolean password = false;
754 int inputType = EditorInfo.TYPE_NULL;
755
Dianne Hackbornab0f4852011-09-12 16:59:06 -0700756 a = theme.obtainStyledAttributes(
757 attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
758
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759 int n = a.getIndexCount();
760 for (int i = 0; i < n; i++) {
761 int attr = a.getIndex(i);
762
763 switch (attr) {
764 case com.android.internal.R.styleable.TextView_editable:
765 editable = a.getBoolean(attr, editable);
766 break;
767
768 case com.android.internal.R.styleable.TextView_inputMethod:
769 inputMethod = a.getText(attr);
770 break;
771
772 case com.android.internal.R.styleable.TextView_numeric:
773 numeric = a.getInt(attr, numeric);
774 break;
775
776 case com.android.internal.R.styleable.TextView_digits:
777 digits = a.getText(attr);
778 break;
779
780 case com.android.internal.R.styleable.TextView_phoneNumber:
781 phone = a.getBoolean(attr, phone);
782 break;
783
784 case com.android.internal.R.styleable.TextView_autoText:
785 autotext = a.getBoolean(attr, autotext);
786 break;
787
788 case com.android.internal.R.styleable.TextView_capitalize:
789 autocap = a.getInt(attr, autocap);
790 break;
791
792 case com.android.internal.R.styleable.TextView_bufferType:
793 buffertype = a.getInt(attr, buffertype);
794 break;
795
796 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
797 selectallonfocus = a.getBoolean(attr, selectallonfocus);
798 break;
799
800 case com.android.internal.R.styleable.TextView_autoLink:
801 mAutoLinkMask = a.getInt(attr, 0);
802 break;
803
804 case com.android.internal.R.styleable.TextView_linksClickable:
805 mLinksClickable = a.getBoolean(attr, true);
806 break;
807
808 case com.android.internal.R.styleable.TextView_drawableLeft:
809 drawableLeft = a.getDrawable(attr);
810 break;
811
812 case com.android.internal.R.styleable.TextView_drawableTop:
813 drawableTop = a.getDrawable(attr);
814 break;
815
816 case com.android.internal.R.styleable.TextView_drawableRight:
817 drawableRight = a.getDrawable(attr);
818 break;
819
820 case com.android.internal.R.styleable.TextView_drawableBottom:
821 drawableBottom = a.getDrawable(attr);
822 break;
823
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -0700824 case com.android.internal.R.styleable.TextView_drawableStart:
825 drawableStart = a.getDrawable(attr);
826 break;
827
828 case com.android.internal.R.styleable.TextView_drawableEnd:
829 drawableEnd = a.getDrawable(attr);
830 break;
831
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800832 case com.android.internal.R.styleable.TextView_drawablePadding:
833 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
834 break;
835
836 case com.android.internal.R.styleable.TextView_maxLines:
837 setMaxLines(a.getInt(attr, -1));
838 break;
839
840 case com.android.internal.R.styleable.TextView_maxHeight:
841 setMaxHeight(a.getDimensionPixelSize(attr, -1));
842 break;
843
844 case com.android.internal.R.styleable.TextView_lines:
845 setLines(a.getInt(attr, -1));
846 break;
847
848 case com.android.internal.R.styleable.TextView_height:
849 setHeight(a.getDimensionPixelSize(attr, -1));
850 break;
851
852 case com.android.internal.R.styleable.TextView_minLines:
853 setMinLines(a.getInt(attr, -1));
854 break;
855
856 case com.android.internal.R.styleable.TextView_minHeight:
857 setMinHeight(a.getDimensionPixelSize(attr, -1));
858 break;
859
860 case com.android.internal.R.styleable.TextView_maxEms:
861 setMaxEms(a.getInt(attr, -1));
862 break;
863
864 case com.android.internal.R.styleable.TextView_maxWidth:
865 setMaxWidth(a.getDimensionPixelSize(attr, -1));
866 break;
867
868 case com.android.internal.R.styleable.TextView_ems:
869 setEms(a.getInt(attr, -1));
870 break;
871
872 case com.android.internal.R.styleable.TextView_width:
873 setWidth(a.getDimensionPixelSize(attr, -1));
874 break;
875
876 case com.android.internal.R.styleable.TextView_minEms:
877 setMinEms(a.getInt(attr, -1));
878 break;
879
880 case com.android.internal.R.styleable.TextView_minWidth:
881 setMinWidth(a.getDimensionPixelSize(attr, -1));
882 break;
883
884 case com.android.internal.R.styleable.TextView_gravity:
885 setGravity(a.getInt(attr, -1));
886 break;
887
888 case com.android.internal.R.styleable.TextView_hint:
Romain Guy4dc4f732009-06-19 15:16:40 -0700889 hint = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800890 break;
891
892 case com.android.internal.R.styleable.TextView_text:
893 text = a.getText(attr);
894 break;
895
896 case com.android.internal.R.styleable.TextView_scrollHorizontally:
897 if (a.getBoolean(attr, false)) {
898 setHorizontallyScrolling(true);
899 }
900 break;
901
902 case com.android.internal.R.styleable.TextView_singleLine:
903 singleLine = a.getBoolean(attr, singleLine);
904 break;
905
906 case com.android.internal.R.styleable.TextView_ellipsize:
907 ellipsize = a.getInt(attr, ellipsize);
908 break;
909
910 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
911 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
912 break;
913
914 case com.android.internal.R.styleable.TextView_includeFontPadding:
915 if (!a.getBoolean(attr, true)) {
916 setIncludeFontPadding(false);
917 }
918 break;
919
920 case com.android.internal.R.styleable.TextView_cursorVisible:
921 if (!a.getBoolean(attr, true)) {
922 setCursorVisible(false);
923 }
924 break;
925
926 case com.android.internal.R.styleable.TextView_maxLength:
927 maxlength = a.getInt(attr, -1);
928 break;
929
930 case com.android.internal.R.styleable.TextView_textScaleX:
931 setTextScaleX(a.getFloat(attr, 1.0f));
932 break;
933
934 case com.android.internal.R.styleable.TextView_freezesText:
935 mFreezesText = a.getBoolean(attr, false);
936 break;
937
938 case com.android.internal.R.styleable.TextView_shadowColor:
939 shadowcolor = a.getInt(attr, 0);
940 break;
941
942 case com.android.internal.R.styleable.TextView_shadowDx:
943 dx = a.getFloat(attr, 0);
944 break;
945
946 case com.android.internal.R.styleable.TextView_shadowDy:
947 dy = a.getFloat(attr, 0);
948 break;
949
950 case com.android.internal.R.styleable.TextView_shadowRadius:
951 r = a.getFloat(attr, 0);
952 break;
953
954 case com.android.internal.R.styleable.TextView_enabled:
955 setEnabled(a.getBoolean(attr, isEnabled()));
956 break;
957
958 case com.android.internal.R.styleable.TextView_textColorHighlight:
959 textColorHighlight = a.getColor(attr, textColorHighlight);
960 break;
961
962 case com.android.internal.R.styleable.TextView_textColor:
963 textColor = a.getColorStateList(attr);
964 break;
965
966 case com.android.internal.R.styleable.TextView_textColorHint:
967 textColorHint = a.getColorStateList(attr);
968 break;
969
970 case com.android.internal.R.styleable.TextView_textColorLink:
971 textColorLink = a.getColorStateList(attr);
972 break;
973
974 case com.android.internal.R.styleable.TextView_textSize:
975 textSize = a.getDimensionPixelSize(attr, textSize);
976 break;
977
978 case com.android.internal.R.styleable.TextView_typeface:
979 typefaceIndex = a.getInt(attr, typefaceIndex);
980 break;
981
982 case com.android.internal.R.styleable.TextView_textStyle:
983 styleIndex = a.getInt(attr, styleIndex);
984 break;
985
Raph Leviend570e892012-05-09 11:45:34 -0700986 case com.android.internal.R.styleable.TextView_fontFamily:
987 fontFamily = a.getString(attr);
988 break;
989
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 case com.android.internal.R.styleable.TextView_password:
991 password = a.getBoolean(attr, password);
992 break;
993
994 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
995 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
996 break;
997
998 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
999 mSpacingMult = a.getFloat(attr, mSpacingMult);
1000 break;
1001
1002 case com.android.internal.R.styleable.TextView_inputType:
Gilles Debunne60e21862012-01-30 15:04:14 -08001003 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001004 break;
1005
1006 case com.android.internal.R.styleable.TextView_imeOptions:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001007 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001008 mEditor.createInputContentTypeIfNeeded();
1009 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1010 mEditor.mInputContentType.imeOptions);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 break;
1012
1013 case com.android.internal.R.styleable.TextView_imeActionLabel:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001014 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001015 mEditor.createInputContentTypeIfNeeded();
1016 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 break;
1018
1019 case com.android.internal.R.styleable.TextView_imeActionId:
Gilles Debunne5fae9962012-05-08 14:53:20 -07001020 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001021 mEditor.createInputContentTypeIfNeeded();
1022 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1023 mEditor.mInputContentType.imeActionId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001024 break;
1025
1026 case com.android.internal.R.styleable.TextView_privateImeOptions:
1027 setPrivateImeOptions(a.getString(attr));
1028 break;
1029
1030 case com.android.internal.R.styleable.TextView_editorExtras:
1031 try {
1032 setInputExtras(a.getResourceId(attr, 0));
1033 } catch (XmlPullParserException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001034 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001035 } catch (IOException e) {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001036 Log.w(LOG_TAG, "Failure reading input extras", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001037 }
1038 break;
Adam Powellb08013c2010-09-16 16:28:11 -07001039
Gilles Debunnef75c97e2011-02-10 16:09:53 -08001040 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1041 mCursorDrawableRes = a.getResourceId(attr, 0);
1042 break;
1043
Adam Powellb08013c2010-09-16 16:28:11 -07001044 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1045 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1046 break;
1047
1048 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1049 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1050 break;
1051
1052 case com.android.internal.R.styleable.TextView_textSelectHandle:
1053 mTextSelectHandleRes = a.getResourceId(attr, 0);
1054 break;
Gilles Debunne7b9652b2010-10-26 16:27:12 -07001055
Gilles Debunne69340442011-03-31 13:37:51 -07001056 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1057 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1058 break;
1059
Gilles Debunne86b9c782010-11-11 10:43:48 -08001060 case com.android.internal.R.styleable.TextView_textIsSelectable:
Gilles Debunne60e21862012-01-30 15:04:14 -08001061 setTextIsSelectable(a.getBoolean(attr, false));
Gilles Debunne86b9c782010-11-11 10:43:48 -08001062 break;
Gilles Debunnef3a135b2011-05-23 16:28:47 -07001063
Adam Powell7f8f79a2011-07-07 18:35:54 -07001064 case com.android.internal.R.styleable.TextView_textAllCaps:
1065 allCaps = a.getBoolean(attr, false);
1066 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 }
1068 }
1069 a.recycle();
1070
1071 BufferType bufferType = BufferType.EDITABLE;
1072
Gilles Debunned7483bf2010-11-10 10:47:45 -08001073 final int variation =
1074 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1075 final boolean passwordInputType = variation
1076 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1077 final boolean webPasswordInputType = variation
1078 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
Ken Wakasa82d731a2010-12-24 23:42:41 +09001079 final boolean numberPasswordInputType = variation
1080 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001081
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 if (inputMethod != null) {
Gilles Debunnee15b3582010-06-16 15:17:21 -07001083 Class<?> c;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084
1085 try {
1086 c = Class.forName(inputMethod.toString());
1087 } catch (ClassNotFoundException ex) {
1088 throw new RuntimeException(ex);
1089 }
1090
1091 try {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001092 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001093 mEditor.mKeyListener = (KeyListener) c.newInstance();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 } catch (InstantiationException ex) {
1095 throw new RuntimeException(ex);
1096 } catch (IllegalAccessException ex) {
1097 throw new RuntimeException(ex);
1098 }
1099 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001100 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001101 ? inputType
Gilles Debunne2d373a12012-04-20 15:32:19 -07001102 : mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001103 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001104 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 }
1106 } else if (digits != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001107 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001108 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001109 // If no input type was specified, we will default to generic
1110 // text, since we can't tell the IME about the set of digits
1111 // that was selected.
Gilles Debunne2d373a12012-04-20 15:32:19 -07001112 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
Dianne Hackborn7ed6ee52009-09-10 18:41:28 -07001113 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 } else if (inputType != EditorInfo.TYPE_NULL) {
1115 setInputType(inputType, true);
Gilles Debunne91a08cf2010-11-08 17:34:49 -08001116 // If set, the input type overrides what was set using the deprecated singleLine flag.
1117 singleLine = !isMultilineInputType(inputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 } else if (phone) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001119 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001120 mEditor.mKeyListener = DialerKeyListener.getInstance();
1121 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001122 } else if (numeric != 0) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001123 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001124 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001125 (numeric & DECIMAL) != 0);
1126 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1127 if ((numeric & SIGNED) != 0) {
1128 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1129 }
1130 if ((numeric & DECIMAL) != 0) {
1131 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1132 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07001133 mEditor.mInputType = inputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001134 } else if (autotext || autocap != -1) {
1135 TextKeyListener.Capitalize cap;
1136
1137 inputType = EditorInfo.TYPE_CLASS_TEXT;
Gilles Debunnef95449d2010-11-05 13:54:13 -07001138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 switch (autocap) {
1140 case 1:
1141 cap = TextKeyListener.Capitalize.SENTENCES;
1142 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1143 break;
1144
1145 case 2:
1146 cap = TextKeyListener.Capitalize.WORDS;
1147 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1148 break;
1149
1150 case 3:
1151 cap = TextKeyListener.Capitalize.CHARACTERS;
1152 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1153 break;
1154
1155 default:
1156 cap = TextKeyListener.Capitalize.NONE;
1157 break;
1158 }
1159
Gilles Debunne5fae9962012-05-08 14:53:20 -07001160 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001161 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1162 mEditor.mInputType = inputType;
Gilles Debunne60e21862012-01-30 15:04:14 -08001163 } else if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08001164 // Prevent text changes from keyboard.
Gilles Debunne60e21862012-01-30 15:04:14 -08001165 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001166 mEditor.mKeyListener = null;
1167 mEditor.mInputType = EditorInfo.TYPE_NULL;
Gilles Debunne60e21862012-01-30 15:04:14 -08001168 }
Gilles Debunne86b9c782010-11-11 10:43:48 -08001169 bufferType = BufferType.SPANNABLE;
Gilles Debunne86b9c782010-11-11 10:43:48 -08001170 // So that selection can be changed using arrow keys and touch is handled.
1171 setMovementMethod(ArrowKeyMovementMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001172 } else if (editable) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001173 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001174 mEditor.mKeyListener = TextKeyListener.getInstance();
1175 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001176 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001177 if (mEditor != null) mEditor.mKeyListener = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001178
1179 switch (buffertype) {
1180 case 0:
1181 bufferType = BufferType.NORMAL;
1182 break;
1183 case 1:
1184 bufferType = BufferType.SPANNABLE;
1185 break;
1186 case 2:
1187 bufferType = BufferType.EDITABLE;
1188 break;
1189 }
1190 }
1191
Gilles Debunne2d373a12012-04-20 15:32:19 -07001192 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1193 webPasswordInputType, numberPasswordInputType);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194
1195 if (selectallonfocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001196 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001197 mEditor.mSelectAllOnFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001198
1199 if (bufferType == BufferType.NORMAL)
1200 bufferType = BufferType.SPANNABLE;
1201 }
1202
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001203 // This call will save the initial left/right drawables
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204 setCompoundDrawablesWithIntrinsicBounds(
1205 drawableLeft, drawableTop, drawableRight, drawableBottom);
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001206 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 setCompoundDrawablePadding(drawablePadding);
1208
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08001209 // Same as setSingleLine(), but make sure the transformation method and the maximum number
Gilles Debunne066460f2010-12-15 17:31:51 -08001210 // of lines of height are unchanged for multi-line TextViews.
Gilles Debunned7483bf2010-11-10 10:47:45 -08001211 setInputTypeSingleLine(singleLine);
Gilles Debunne066460f2010-12-15 17:31:51 -08001212 applySingleLine(singleLine, singleLine, singleLine);
Gilles Debunned7483bf2010-11-10 10:47:45 -08001213
Gilles Debunne60e21862012-01-30 15:04:14 -08001214 if (singleLine && getKeyListener() == null && ellipsize < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 ellipsize = 3; // END
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 }
1217
1218 switch (ellipsize) {
1219 case 1:
1220 setEllipsize(TextUtils.TruncateAt.START);
1221 break;
1222 case 2:
1223 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1224 break;
1225 case 3:
1226 setEllipsize(TextUtils.TruncateAt.END);
1227 break;
1228 case 4:
Adam Powell282e3772011-08-30 16:51:11 -07001229 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1230 setHorizontalFadingEdgeEnabled(true);
1231 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1232 } else {
1233 setHorizontalFadingEdgeEnabled(false);
1234 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1235 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1237 break;
1238 }
1239
1240 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1241 setHintTextColor(textColorHint);
1242 setLinkTextColor(textColorLink);
1243 if (textColorHighlight != 0) {
1244 setHighlightColor(textColorHighlight);
1245 }
1246 setRawTextSize(textSize);
1247
Adam Powell7f8f79a2011-07-07 18:35:54 -07001248 if (allCaps) {
1249 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1250 }
1251
Ken Wakasa82d731a2010-12-24 23:42:41 +09001252 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001253 setTransformationMethod(PasswordTransformationMethod.getInstance());
1254 typefaceIndex = MONOSPACE;
Gilles Debunne2d373a12012-04-20 15:32:19 -07001255 } else if (mEditor != null &&
1256 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
Gilles Debunned7483bf2010-11-10 10:47:45 -08001257 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001258 typefaceIndex = MONOSPACE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001259 }
1260
Raph Leviend570e892012-05-09 11:45:34 -07001261 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001262
1263 if (shadowcolor != 0) {
1264 setShadowLayer(r, dx, dy, shadowcolor);
1265 }
1266
1267 if (maxlength >= 0) {
1268 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1269 } else {
1270 setFilters(NO_FILTERS);
1271 }
1272
1273 setText(text, bufferType);
Romain Guy4dc4f732009-06-19 15:16:40 -07001274 if (hint != null) setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001275
1276 /*
1277 * Views are not normally focusable unless specified to be.
1278 * However, TextViews that have input or movement methods *are*
1279 * focusable by default.
1280 */
1281 a = context.obtainStyledAttributes(attrs,
1282 com.android.internal.R.styleable.View,
1283 defStyle, 0);
1284
Gilles Debunne60e21862012-01-30 15:04:14 -08001285 boolean focusable = mMovement != null || getKeyListener() != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001286 boolean clickable = focusable;
1287 boolean longClickable = focusable;
1288
1289 n = a.getIndexCount();
1290 for (int i = 0; i < n; i++) {
1291 int attr = a.getIndex(i);
1292
1293 switch (attr) {
1294 case com.android.internal.R.styleable.View_focusable:
1295 focusable = a.getBoolean(attr, focusable);
1296 break;
1297
1298 case com.android.internal.R.styleable.View_clickable:
1299 clickable = a.getBoolean(attr, clickable);
1300 break;
1301
1302 case com.android.internal.R.styleable.View_longClickable:
1303 longClickable = a.getBoolean(attr, longClickable);
1304 break;
1305 }
1306 }
1307 a.recycle();
1308
1309 setFocusable(focusable);
1310 setClickable(clickable);
1311 setLongClickable(longClickable);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07001312
Gilles Debunned88876a2012-03-16 17:34:04 -07001313 if (mEditor != null) mEditor.prepareCursorControllers();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001314
1315 // If not explicitly specified this view is important for accessibility.
1316 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1317 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1318 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001319 }
1320
Raph Leviend570e892012-05-09 11:45:34 -07001321 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001322 Typeface tf = null;
Raph Leviend570e892012-05-09 11:45:34 -07001323 if (familyName != null) {
1324 tf = Typeface.create(familyName, styleIndex);
1325 if (tf != null) {
1326 setTypeface(tf);
1327 return;
1328 }
1329 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 switch (typefaceIndex) {
1331 case SANS:
1332 tf = Typeface.SANS_SERIF;
1333 break;
1334
1335 case SERIF:
1336 tf = Typeface.SERIF;
1337 break;
1338
1339 case MONOSPACE:
1340 tf = Typeface.MONOSPACE;
1341 break;
1342 }
1343
1344 setTypeface(tf, styleIndex);
1345 }
1346
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001347 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1348 boolean hasRelativeDrawables = (start != null) || (end != null);
1349 if (hasRelativeDrawables) {
1350 Drawables dr = mDrawables;
1351 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001352 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001353 }
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001354 mDrawables.mOverride = true;
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001355 final Rect compoundRect = dr.mCompoundRect;
1356 int[] state = getDrawableState();
1357 if (start != null) {
1358 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1359 start.setState(state);
1360 start.copyBounds(compoundRect);
1361 start.setCallback(this);
1362
1363 dr.mDrawableStart = start;
1364 dr.mDrawableSizeStart = compoundRect.width();
1365 dr.mDrawableHeightStart = compoundRect.height();
1366 } else {
1367 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1368 }
1369 if (end != null) {
1370 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1371 end.setState(state);
1372 end.copyBounds(compoundRect);
1373 end.setCallback(this);
1374
1375 dr.mDrawableEnd = end;
1376 dr.mDrawableSizeEnd = compoundRect.width();
1377 dr.mDrawableHeightEnd = compoundRect.height();
1378 } else {
1379 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1380 }
1381 }
1382 }
1383
Janos Levai042856c2010-10-15 02:53:58 +03001384 @Override
1385 public void setEnabled(boolean enabled) {
1386 if (enabled == isEnabled()) {
1387 return;
1388 }
1389
1390 if (!enabled) {
1391 // Hide the soft input if the currently active TextView is disabled
1392 InputMethodManager imm = InputMethodManager.peekInstance();
1393 if (imm != null && imm.isActive(this)) {
1394 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1395 }
1396 }
Gilles Debunne545c4d42011-11-29 10:37:15 -08001397
Janos Levai042856c2010-10-15 02:53:58 +03001398 super.setEnabled(enabled);
Gilles Debunne545c4d42011-11-29 10:37:15 -08001399
Dianne Hackbornbc823852011-09-18 17:19:50 -07001400 if (enabled) {
1401 // Make sure IME is updated with current editor info.
1402 InputMethodManager imm = InputMethodManager.peekInstance();
1403 if (imm != null) imm.restartInput(this);
1404 }
Mark Wagnerf8185112011-10-25 16:33:41 -07001405
Gilles Debunne33b7de852012-03-12 11:57:48 -07001406 // Will change text color
Gilles Debunned88876a2012-03-16 17:34:04 -07001407 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001408 mEditor.invalidateTextDisplayList();
1409 mEditor.prepareCursorControllers();
Gilles Debunne545c4d42011-11-29 10:37:15 -08001410
Gilles Debunned88876a2012-03-16 17:34:04 -07001411 // start or stop the cursor blinking as appropriate
Gilles Debunne2d373a12012-04-20 15:32:19 -07001412 mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07001413 }
Janos Levai042856c2010-10-15 02:53:58 +03001414 }
1415
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416 /**
1417 * Sets the typeface and style in which the text should be displayed,
1418 * and turns on the fake bold and italic bits in the Paint if the
1419 * Typeface that you provided does not have all the bits in the
1420 * style that you specified.
1421 *
1422 * @attr ref android.R.styleable#TextView_typeface
1423 * @attr ref android.R.styleable#TextView_textStyle
1424 */
1425 public void setTypeface(Typeface tf, int style) {
1426 if (style > 0) {
1427 if (tf == null) {
1428 tf = Typeface.defaultFromStyle(style);
1429 } else {
1430 tf = Typeface.create(tf, style);
1431 }
1432
1433 setTypeface(tf);
1434 // now compute what (if any) algorithmic styling is needed
1435 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1436 int need = style & ~typefaceStyle;
1437 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1438 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1439 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -07001440 mTextPaint.setFakeBoldText(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001441 mTextPaint.setTextSkewX(0);
1442 setTypeface(tf);
1443 }
1444 }
1445
1446 /**
1447 * Subclasses override this to specify that they have a KeyListener
1448 * by default even if not specifically called for in the XML options.
1449 */
1450 protected boolean getDefaultEditable() {
1451 return false;
1452 }
1453
1454 /**
1455 * Subclasses override this to specify a default movement method.
1456 */
1457 protected MovementMethod getDefaultMovementMethod() {
1458 return null;
1459 }
1460
1461 /**
1462 * Return the text the TextView is displaying. If setText() was called with
1463 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1464 * the return value from this method to Spannable or Editable, respectively.
1465 *
1466 * Note: The content of the return value should not be modified. If you want
1467 * a modifiable one, you should make your own copy first.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001468 *
1469 * @attr ref android.R.styleable#TextView_text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470 */
1471 @ViewDebug.CapturedViewProperty
1472 public CharSequence getText() {
1473 return mText;
1474 }
1475
1476 /**
1477 * Returns the length, in characters, of the text managed by this TextView
1478 */
1479 public int length() {
1480 return mText.length();
1481 }
1482
1483 /**
1484 * Return the text the TextView is displaying as an Editable object. If
1485 * the text is not editable, null is returned.
1486 *
1487 * @see #getText
1488 */
1489 public Editable getEditableText() {
1490 return (mText instanceof Editable) ? (Editable)mText : null;
1491 }
1492
1493 /**
1494 * @return the height of one standard line in pixels. Note that markup
1495 * within the text can cause individual lines to be taller or shorter
1496 * than this height, and the layout may contain additional first-
1497 * or last-line padding.
1498 */
1499 public int getLineHeight() {
Gilles Debunne96e6b8b2010-12-14 13:43:45 -08001500 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 }
1502
1503 /**
1504 * @return the Layout that is currently being used to display the text.
1505 * This can be null if the text or width has recently changes.
1506 */
1507 public final Layout getLayout() {
1508 return mLayout;
1509 }
1510
1511 /**
Fabrice Di Meglio0ed59fa2012-05-29 20:32:51 -07001512 * @return the Layout that is currently being used to display the hint text.
1513 * This can be null.
1514 */
1515 final Layout getHintLayout() {
1516 return mHintLayout;
1517 }
1518
1519 /**
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07001520 * Retrieve the {@link android.content.UndoManager} that is currently associated
1521 * with this TextView. By default there is no associated UndoManager, so null
1522 * is returned. One can be associated with the TextView through
1523 * {@link #setUndoManager(android.content.UndoManager, String)}
1524 */
1525 public final UndoManager getUndoManager() {
1526 return mEditor == null ? null : mEditor.mUndoManager;
1527 }
1528
1529 /**
1530 * Associate an {@link android.content.UndoManager} with this TextView. Once
1531 * done, all edit operations on the TextView will result in appropriate
1532 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1533 * stack.
1534 *
1535 * @param undoManager The {@link android.content.UndoManager} to associate with
1536 * this TextView, or null to clear any existing association.
1537 * @param tag String tag identifying this particular TextView owner in the
1538 * UndoManager. This is used to keep the correct association with the
1539 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1540 */
1541 public final void setUndoManager(UndoManager undoManager, String tag) {
1542 if (undoManager != null) {
1543 createEditorIfNeeded();
1544 mEditor.mUndoManager = undoManager;
1545 mEditor.mUndoOwner = undoManager.getOwner(tag, this);
1546 mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor);
1547 if (!(mText instanceof Editable)) {
1548 setText(mText, BufferType.EDITABLE);
1549 }
1550
1551 setFilters((Editable) mText, mFilters);
1552 } else if (mEditor != null) {
1553 // XXX need to destroy all associated state.
1554 mEditor.mUndoManager = null;
1555 mEditor.mUndoOwner = null;
1556 mEditor.mUndoInputFilter = null;
1557 }
1558 }
1559
1560 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001561 * @return the current key listener for this TextView.
1562 * This will frequently be null for non-EditText TextViews.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001563 *
1564 * @attr ref android.R.styleable#TextView_numeric
1565 * @attr ref android.R.styleable#TextView_digits
1566 * @attr ref android.R.styleable#TextView_phoneNumber
1567 * @attr ref android.R.styleable#TextView_inputMethod
1568 * @attr ref android.R.styleable#TextView_capitalize
1569 * @attr ref android.R.styleable#TextView_autoText
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001570 */
1571 public final KeyListener getKeyListener() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001572 return mEditor == null ? null : mEditor.mKeyListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001573 }
1574
1575 /**
1576 * Sets the key listener to be used with this TextView. This can be null
1577 * to disallow user input. Note that this method has significant and
1578 * subtle interactions with soft keyboards and other input method:
1579 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1580 * for important details. Calling this method will replace the current
1581 * content type of the text view with the content type returned by the
1582 * key listener.
1583 * <p>
1584 * Be warned that if you want a TextView with a key listener or movement
1585 * method not to be focusable, or if you want a TextView without a
1586 * key listener or movement method to be focusable, you must call
1587 * {@link #setFocusable} again after calling this to get the focusability
1588 * back the way you want it.
1589 *
1590 * @attr ref android.R.styleable#TextView_numeric
1591 * @attr ref android.R.styleable#TextView_digits
1592 * @attr ref android.R.styleable#TextView_phoneNumber
1593 * @attr ref android.R.styleable#TextView_inputMethod
1594 * @attr ref android.R.styleable#TextView_capitalize
1595 * @attr ref android.R.styleable#TextView_autoText
1596 */
1597 public void setKeyListener(KeyListener input) {
1598 setKeyListenerOnly(input);
1599 fixFocusableAndClickableSettings();
1600
1601 if (input != null) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07001602 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001603 try {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001604 mEditor.mInputType = mEditor.mKeyListener.getInputType();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001605 } catch (IncompatibleClassChangeError e) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001606 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001607 }
Gilles Debunned7483bf2010-11-10 10:47:45 -08001608 // Change inputType, without affecting transformation.
1609 // No need to applySingleLine since mSingleLine is unchanged.
1610 setInputTypeSingleLine(mSingleLine);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001611 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001612 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001613 }
1614
1615 InputMethodManager imm = InputMethodManager.peekInstance();
1616 if (imm != null) imm.restartInput(this);
1617 }
1618
1619 private void setKeyListenerOnly(KeyListener input) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001620 if (mEditor == null && input == null) return; // null is the default value
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001621
Gilles Debunne5fae9962012-05-08 14:53:20 -07001622 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07001623 if (mEditor.mKeyListener != input) {
1624 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08001625 if (input != null && !(mText instanceof Editable)) {
1626 setText(mText);
1627 }
1628
1629 setFilters((Editable) mText, mFilters);
1630 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001631 }
1632
1633 /**
1634 * @return the movement method being used for this TextView.
1635 * This will frequently be null for non-EditText TextViews.
1636 */
1637 public final MovementMethod getMovementMethod() {
1638 return mMovement;
1639 }
1640
1641 /**
1642 * Sets the movement method (arrow key handler) to be used for
1643 * this TextView. This can be null to disallow using the arrow keys
1644 * to move the cursor or scroll the view.
1645 * <p>
1646 * Be warned that if you want a TextView with a key listener or movement
1647 * method not to be focusable, or if you want a TextView without a
1648 * key listener or movement method to be focusable, you must call
1649 * {@link #setFocusable} again after calling this to get the focusability
1650 * back the way you want it.
1651 */
1652 public final void setMovementMethod(MovementMethod movement) {
Gilles Debunne60e21862012-01-30 15:04:14 -08001653 if (mMovement != movement) {
1654 mMovement = movement;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655
Gilles Debunne60e21862012-01-30 15:04:14 -08001656 if (movement != null && !(mText instanceof Spannable)) {
1657 setText(mText);
1658 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001659
Gilles Debunne60e21862012-01-30 15:04:14 -08001660 fixFocusableAndClickableSettings();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07001661
Gilles Debunne2d373a12012-04-20 15:32:19 -07001662 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1663 // mMovement
1664 if (mEditor != null) mEditor.prepareCursorControllers();
Gilles Debunne60e21862012-01-30 15:04:14 -08001665 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001666 }
1667
1668 private void fixFocusableAndClickableSettings() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07001669 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001670 setFocusable(true);
1671 setClickable(true);
1672 setLongClickable(true);
1673 } else {
1674 setFocusable(false);
1675 setClickable(false);
1676 setLongClickable(false);
1677 }
1678 }
1679
1680 /**
1681 * @return the current transformation method for this TextView.
1682 * This will frequently be null except for single-line and password
1683 * fields.
Gilles Debunnef03acef2012-04-30 19:26:19 -07001684 *
1685 * @attr ref android.R.styleable#TextView_password
1686 * @attr ref android.R.styleable#TextView_singleLine
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001687 */
1688 public final TransformationMethod getTransformationMethod() {
1689 return mTransformation;
1690 }
1691
1692 /**
1693 * Sets the transformation that is applied to the text that this
1694 * TextView is displaying.
1695 *
1696 * @attr ref android.R.styleable#TextView_password
1697 * @attr ref android.R.styleable#TextView_singleLine
1698 */
1699 public final void setTransformationMethod(TransformationMethod method) {
1700 if (method == mTransformation) {
1701 // Avoid the setText() below if the transformation is
1702 // the same.
1703 return;
1704 }
1705 if (mTransformation != null) {
1706 if (mText instanceof Spannable) {
1707 ((Spannable) mText).removeSpan(mTransformation);
1708 }
1709 }
1710
1711 mTransformation = method;
1712
Adam Powell7f8f79a2011-07-07 18:35:54 -07001713 if (method instanceof TransformationMethod2) {
1714 TransformationMethod2 method2 = (TransformationMethod2) method;
Gilles Debunne60e21862012-01-30 15:04:14 -08001715 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
Adam Powell7f8f79a2011-07-07 18:35:54 -07001716 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1717 } else {
1718 mAllowTransformationLengthChange = false;
1719 }
1720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001721 setText(mText);
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001722
1723 if (hasPasswordTransformationMethod()) {
Svetoslav6254f482013-06-04 17:22:14 -07001724 notifyViewAccessibilityStateChangedIfNeeded();
Svetoslav Ganovc406be92012-05-11 16:12:32 -07001725 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001726 }
1727
1728 /**
1729 * Returns the top padding of the view, plus space for the top
1730 * Drawable if any.
1731 */
1732 public int getCompoundPaddingTop() {
1733 final Drawables dr = mDrawables;
1734 if (dr == null || dr.mDrawableTop == null) {
1735 return mPaddingTop;
1736 } else {
1737 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1738 }
1739 }
1740
1741 /**
1742 * Returns the bottom padding of the view, plus space for the bottom
1743 * Drawable if any.
1744 */
1745 public int getCompoundPaddingBottom() {
1746 final Drawables dr = mDrawables;
1747 if (dr == null || dr.mDrawableBottom == null) {
1748 return mPaddingBottom;
1749 } else {
1750 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1751 }
1752 }
1753
1754 /**
1755 * Returns the left padding of the view, plus space for the left
1756 * Drawable if any.
1757 */
1758 public int getCompoundPaddingLeft() {
1759 final Drawables dr = mDrawables;
1760 if (dr == null || dr.mDrawableLeft == null) {
1761 return mPaddingLeft;
1762 } else {
1763 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1764 }
1765 }
1766
1767 /**
1768 * Returns the right padding of the view, plus space for the right
1769 * Drawable if any.
1770 */
1771 public int getCompoundPaddingRight() {
1772 final Drawables dr = mDrawables;
1773 if (dr == null || dr.mDrawableRight == null) {
1774 return mPaddingRight;
1775 } else {
1776 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1777 }
1778 }
1779
1780 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001781 * Returns the start padding of the view, plus space for the start
1782 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001783 */
1784 public int getCompoundPaddingStart() {
1785 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001786 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001787 default:
1788 case LAYOUT_DIRECTION_LTR:
1789 return getCompoundPaddingLeft();
1790 case LAYOUT_DIRECTION_RTL:
1791 return getCompoundPaddingRight();
1792 }
1793 }
1794
1795 /**
1796 * Returns the end padding of the view, plus space for the end
1797 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001798 */
1799 public int getCompoundPaddingEnd() {
1800 resolveDrawables();
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07001801 switch(getLayoutDirection()) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001802 default:
1803 case LAYOUT_DIRECTION_LTR:
1804 return getCompoundPaddingRight();
1805 case LAYOUT_DIRECTION_RTL:
1806 return getCompoundPaddingLeft();
1807 }
1808 }
1809
1810 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001811 * Returns the extended top padding of the view, including both the
1812 * top Drawable if any and any extra space to keep more than maxLines
1813 * of text from showing. It is only valid to call this after measuring.
1814 */
1815 public int getExtendedPaddingTop() {
1816 if (mMaxMode != LINES) {
1817 return getCompoundPaddingTop();
1818 }
1819
1820 if (mLayout.getLineCount() <= mMaximum) {
1821 return getCompoundPaddingTop();
1822 }
1823
1824 int top = getCompoundPaddingTop();
1825 int bottom = getCompoundPaddingBottom();
1826 int viewht = getHeight() - top - bottom;
1827 int layoutht = mLayout.getLineTop(mMaximum);
1828
1829 if (layoutht >= viewht) {
1830 return top;
1831 }
1832
1833 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1834 if (gravity == Gravity.TOP) {
1835 return top;
1836 } else if (gravity == Gravity.BOTTOM) {
1837 return top + viewht - layoutht;
1838 } else { // (gravity == Gravity.CENTER_VERTICAL)
1839 return top + (viewht - layoutht) / 2;
1840 }
1841 }
1842
1843 /**
1844 * Returns the extended bottom padding of the view, including both the
1845 * bottom Drawable if any and any extra space to keep more than maxLines
1846 * of text from showing. It is only valid to call this after measuring.
1847 */
1848 public int getExtendedPaddingBottom() {
1849 if (mMaxMode != LINES) {
1850 return getCompoundPaddingBottom();
1851 }
1852
1853 if (mLayout.getLineCount() <= mMaximum) {
1854 return getCompoundPaddingBottom();
1855 }
1856
1857 int top = getCompoundPaddingTop();
1858 int bottom = getCompoundPaddingBottom();
1859 int viewht = getHeight() - top - bottom;
1860 int layoutht = mLayout.getLineTop(mMaximum);
1861
1862 if (layoutht >= viewht) {
1863 return bottom;
1864 }
1865
1866 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1867 if (gravity == Gravity.TOP) {
1868 return bottom + viewht - layoutht;
1869 } else if (gravity == Gravity.BOTTOM) {
1870 return bottom;
1871 } else { // (gravity == Gravity.CENTER_VERTICAL)
1872 return bottom + (viewht - layoutht) / 2;
1873 }
1874 }
1875
1876 /**
1877 * Returns the total left padding of the view, including the left
1878 * Drawable if any.
1879 */
1880 public int getTotalPaddingLeft() {
1881 return getCompoundPaddingLeft();
1882 }
1883
1884 /**
1885 * Returns the total right padding of the view, including the right
1886 * Drawable if any.
1887 */
1888 public int getTotalPaddingRight() {
1889 return getCompoundPaddingRight();
1890 }
1891
1892 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001893 * Returns the total start padding of the view, including the start
1894 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001895 */
1896 public int getTotalPaddingStart() {
1897 return getCompoundPaddingStart();
1898 }
1899
1900 /**
1901 * Returns the total end padding of the view, including the end
1902 * Drawable if any.
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07001903 */
1904 public int getTotalPaddingEnd() {
1905 return getCompoundPaddingEnd();
1906 }
1907
1908 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001909 * Returns the total top padding of the view, including the top
1910 * Drawable if any, the extra space to keep more than maxLines
1911 * from showing, and the vertical offset for gravity, if any.
1912 */
1913 public int getTotalPaddingTop() {
1914 return getExtendedPaddingTop() + getVerticalOffset(true);
1915 }
1916
1917 /**
1918 * Returns the total bottom padding of the view, including the bottom
1919 * Drawable if any, the extra space to keep more than maxLines
1920 * from showing, and the vertical offset for gravity, if any.
1921 */
1922 public int getTotalPaddingBottom() {
1923 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1924 }
1925
1926 /**
1927 * Sets the Drawables (if any) to appear to the left of, above,
1928 * to the right of, and below the text. Use null if you do not
1929 * want a Drawable there. The Drawables must already have had
1930 * {@link Drawable#setBounds} called.
1931 *
1932 * @attr ref android.R.styleable#TextView_drawableLeft
1933 * @attr ref android.R.styleable#TextView_drawableTop
1934 * @attr ref android.R.styleable#TextView_drawableRight
1935 * @attr ref android.R.styleable#TextView_drawableBottom
1936 */
1937 public void setCompoundDrawables(Drawable left, Drawable top,
1938 Drawable right, Drawable bottom) {
1939 Drawables dr = mDrawables;
1940
1941 final boolean drawables = left != null || top != null
1942 || right != null || bottom != null;
1943
1944 if (!drawables) {
1945 // Clearing drawables... can we free the data structure?
1946 if (dr != null) {
1947 if (dr.mDrawablePadding == 0) {
1948 mDrawables = null;
1949 } else {
1950 // We need to retain the last set padding, so just clear
1951 // out all of the fields in the existing structure.
Romain Guy48540eb2009-05-19 16:44:57 -07001952 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001953 dr.mDrawableLeft = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001954 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001955 dr.mDrawableTop = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001956 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001957 dr.mDrawableRight = null;
Romain Guy48540eb2009-05-19 16:44:57 -07001958 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001959 dr.mDrawableBottom = null;
1960 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
1961 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
1962 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
1963 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
1964 }
1965 }
1966 } else {
1967 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001968 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001969 }
1970
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07001971 mDrawables.mOverride = false;
1972
Romain Guy48540eb2009-05-19 16:44:57 -07001973 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
1974 dr.mDrawableLeft.setCallback(null);
1975 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001976 dr.mDrawableLeft = left;
Romain Guy8e618e52010-03-08 12:18:20 -08001977
1978 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001979 dr.mDrawableTop.setCallback(null);
1980 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001981 dr.mDrawableTop = top;
Romain Guy8e618e52010-03-08 12:18:20 -08001982
1983 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001984 dr.mDrawableRight.setCallback(null);
1985 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001986 dr.mDrawableRight = right;
Romain Guy8e618e52010-03-08 12:18:20 -08001987
1988 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
Romain Guy48540eb2009-05-19 16:44:57 -07001989 dr.mDrawableBottom.setCallback(null);
1990 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001991 dr.mDrawableBottom = bottom;
1992
1993 final Rect compoundRect = dr.mCompoundRect;
Romain Guy48540eb2009-05-19 16:44:57 -07001994 int[] state;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001995
1996 state = getDrawableState();
1997
1998 if (left != null) {
1999 left.setState(state);
2000 left.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002001 left.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002002 dr.mDrawableSizeLeft = compoundRect.width();
2003 dr.mDrawableHeightLeft = compoundRect.height();
2004 } else {
2005 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2006 }
2007
2008 if (right != null) {
2009 right.setState(state);
2010 right.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002011 right.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002012 dr.mDrawableSizeRight = compoundRect.width();
2013 dr.mDrawableHeightRight = compoundRect.height();
2014 } else {
2015 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2016 }
2017
2018 if (top != null) {
2019 top.setState(state);
2020 top.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002021 top.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002022 dr.mDrawableSizeTop = compoundRect.height();
2023 dr.mDrawableWidthTop = compoundRect.width();
2024 } else {
2025 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2026 }
2027
2028 if (bottom != null) {
2029 bottom.setState(state);
2030 bottom.copyBounds(compoundRect);
Romain Guy48540eb2009-05-19 16:44:57 -07002031 bottom.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002032 dr.mDrawableSizeBottom = compoundRect.height();
2033 dr.mDrawableWidthBottom = compoundRect.width();
2034 } else {
2035 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2036 }
2037 }
2038
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002039 // Save initial left/right drawables
2040 if (dr != null) {
2041 dr.mDrawableLeftInitial = left;
2042 dr.mDrawableRightInitial = right;
2043 }
2044
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002045 invalidate();
2046 requestLayout();
2047 }
2048
2049 /**
2050 * Sets the Drawables (if any) to appear to the left of, above,
2051 * to the right of, and below the text. Use 0 if you do not
2052 * want a Drawable there. The Drawables' bounds will be set to
2053 * their intrinsic bounds.
2054 *
2055 * @param left Resource identifier of the left Drawable.
2056 * @param top Resource identifier of the top Drawable.
2057 * @param right Resource identifier of the right Drawable.
2058 * @param bottom Resource identifier of the bottom Drawable.
2059 *
2060 * @attr ref android.R.styleable#TextView_drawableLeft
2061 * @attr ref android.R.styleable#TextView_drawableTop
2062 * @attr ref android.R.styleable#TextView_drawableRight
2063 * @attr ref android.R.styleable#TextView_drawableBottom
2064 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002065 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002066 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
2067 final Resources resources = getContext().getResources();
2068 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
2069 top != 0 ? resources.getDrawable(top) : null,
2070 right != 0 ? resources.getDrawable(right) : null,
2071 bottom != 0 ? resources.getDrawable(bottom) : null);
2072 }
2073
2074 /**
2075 * Sets the Drawables (if any) to appear to the left of, above,
2076 * to the right of, and below the text. Use null if you do not
2077 * want a Drawable there. The Drawables' bounds will be set to
2078 * their intrinsic bounds.
2079 *
2080 * @attr ref android.R.styleable#TextView_drawableLeft
2081 * @attr ref android.R.styleable#TextView_drawableTop
2082 * @attr ref android.R.styleable#TextView_drawableRight
2083 * @attr ref android.R.styleable#TextView_drawableBottom
2084 */
2085 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
2086 Drawable right, Drawable bottom) {
2087
2088 if (left != null) {
2089 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2090 }
2091 if (right != null) {
2092 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2093 }
2094 if (top != null) {
2095 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2096 }
2097 if (bottom != null) {
2098 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2099 }
2100 setCompoundDrawables(left, top, right, bottom);
2101 }
2102
2103 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002104 * Sets the Drawables (if any) to appear to the start of, above,
2105 * to the end of, and below the text. Use null if you do not
2106 * want a Drawable there. The Drawables must already have had
2107 * {@link Drawable#setBounds} called.
2108 *
2109 * @attr ref android.R.styleable#TextView_drawableStart
2110 * @attr ref android.R.styleable#TextView_drawableTop
2111 * @attr ref android.R.styleable#TextView_drawableEnd
2112 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002113 */
2114 public void setCompoundDrawablesRelative(Drawable start, Drawable top,
2115 Drawable end, Drawable bottom) {
2116 Drawables dr = mDrawables;
2117
2118 final boolean drawables = start != null || top != null
2119 || end != null || bottom != null;
2120
2121 if (!drawables) {
2122 // Clearing drawables... can we free the data structure?
2123 if (dr != null) {
2124 if (dr.mDrawablePadding == 0) {
2125 mDrawables = null;
2126 } else {
2127 // We need to retain the last set padding, so just clear
2128 // out all of the fields in the existing structure.
2129 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2130 dr.mDrawableStart = null;
2131 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2132 dr.mDrawableTop = null;
2133 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2134 dr.mDrawableEnd = null;
2135 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2136 dr.mDrawableBottom = null;
2137 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2138 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2139 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2140 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2141 }
2142 }
2143 } else {
2144 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002145 mDrawables = dr = new Drawables(getContext());
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002146 }
2147
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002148 mDrawables.mOverride = true;
2149
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002150 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2151 dr.mDrawableStart.setCallback(null);
2152 }
2153 dr.mDrawableStart = start;
2154
2155 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2156 dr.mDrawableTop.setCallback(null);
2157 }
2158 dr.mDrawableTop = top;
2159
2160 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2161 dr.mDrawableEnd.setCallback(null);
2162 }
2163 dr.mDrawableEnd = end;
2164
2165 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2166 dr.mDrawableBottom.setCallback(null);
2167 }
2168 dr.mDrawableBottom = bottom;
2169
2170 final Rect compoundRect = dr.mCompoundRect;
2171 int[] state;
2172
2173 state = getDrawableState();
2174
2175 if (start != null) {
2176 start.setState(state);
2177 start.copyBounds(compoundRect);
2178 start.setCallback(this);
2179 dr.mDrawableSizeStart = compoundRect.width();
2180 dr.mDrawableHeightStart = compoundRect.height();
2181 } else {
2182 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2183 }
2184
2185 if (end != null) {
2186 end.setState(state);
2187 end.copyBounds(compoundRect);
2188 end.setCallback(this);
2189 dr.mDrawableSizeEnd = compoundRect.width();
2190 dr.mDrawableHeightEnd = compoundRect.height();
2191 } else {
2192 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2193 }
2194
2195 if (top != null) {
2196 top.setState(state);
2197 top.copyBounds(compoundRect);
2198 top.setCallback(this);
2199 dr.mDrawableSizeTop = compoundRect.height();
2200 dr.mDrawableWidthTop = compoundRect.width();
2201 } else {
2202 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2203 }
2204
2205 if (bottom != null) {
2206 bottom.setState(state);
2207 bottom.copyBounds(compoundRect);
2208 bottom.setCallback(this);
2209 dr.mDrawableSizeBottom = compoundRect.height();
2210 dr.mDrawableWidthBottom = compoundRect.width();
2211 } else {
2212 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2213 }
2214 }
2215
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002216 resetResolvedDrawables();
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002217 resolveDrawables();
2218 invalidate();
2219 requestLayout();
2220 }
2221
2222 /**
2223 * Sets the Drawables (if any) to appear to the start of, above,
2224 * to the end of, and below the text. Use 0 if you do not
2225 * want a Drawable there. The Drawables' bounds will be set to
2226 * their intrinsic bounds.
2227 *
2228 * @param start Resource identifier of the start Drawable.
2229 * @param top Resource identifier of the top Drawable.
2230 * @param end Resource identifier of the end Drawable.
2231 * @param bottom Resource identifier of the bottom Drawable.
2232 *
2233 * @attr ref android.R.styleable#TextView_drawableStart
2234 * @attr ref android.R.styleable#TextView_drawableTop
2235 * @attr ref android.R.styleable#TextView_drawableEnd
2236 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002237 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002238 @android.view.RemotableViewMethod
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002239 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2240 int bottom) {
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002241 final Resources resources = getContext().getResources();
2242 setCompoundDrawablesRelativeWithIntrinsicBounds(
2243 start != 0 ? resources.getDrawable(start) : null,
2244 top != 0 ? resources.getDrawable(top) : null,
2245 end != 0 ? resources.getDrawable(end) : null,
2246 bottom != 0 ? resources.getDrawable(bottom) : null);
2247 }
2248
2249 /**
2250 * Sets the Drawables (if any) to appear to the start of, above,
2251 * to the end of, and below the text. Use null if you do not
2252 * want a Drawable there. The Drawables' bounds will be set to
2253 * their intrinsic bounds.
2254 *
2255 * @attr ref android.R.styleable#TextView_drawableStart
2256 * @attr ref android.R.styleable#TextView_drawableTop
2257 * @attr ref android.R.styleable#TextView_drawableEnd
2258 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002259 */
2260 public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
2261 Drawable end, Drawable bottom) {
2262
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002263 if (start != null) {
2264 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2265 }
2266 if (end != null) {
2267 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2268 }
2269 if (top != null) {
2270 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2271 }
2272 if (bottom != null) {
2273 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2274 }
2275 setCompoundDrawablesRelative(start, top, end, bottom);
2276 }
2277
2278 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002279 * Returns drawables for the left, top, right, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002280 *
2281 * @attr ref android.R.styleable#TextView_drawableLeft
2282 * @attr ref android.R.styleable#TextView_drawableTop
2283 * @attr ref android.R.styleable#TextView_drawableRight
2284 * @attr ref android.R.styleable#TextView_drawableBottom
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002285 */
2286 public Drawable[] getCompoundDrawables() {
2287 final Drawables dr = mDrawables;
2288 if (dr != null) {
2289 return new Drawable[] {
2290 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2291 };
2292 } else {
2293 return new Drawable[] { null, null, null, null };
2294 }
2295 }
2296
2297 /**
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002298 * Returns drawables for the start, top, end, and bottom borders.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002299 *
2300 * @attr ref android.R.styleable#TextView_drawableStart
2301 * @attr ref android.R.styleable#TextView_drawableTop
2302 * @attr ref android.R.styleable#TextView_drawableEnd
2303 * @attr ref android.R.styleable#TextView_drawableBottom
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07002304 */
2305 public Drawable[] getCompoundDrawablesRelative() {
2306 final Drawables dr = mDrawables;
2307 if (dr != null) {
2308 return new Drawable[] {
2309 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2310 };
2311 } else {
2312 return new Drawable[] { null, null, null, null };
2313 }
2314 }
2315
2316 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002317 * Sets the size of the padding between the compound drawables and
2318 * the text.
2319 *
2320 * @attr ref android.R.styleable#TextView_drawablePadding
2321 */
Daniel Sandler820ba322012-03-23 16:36:00 -05002322 @android.view.RemotableViewMethod
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002323 public void setCompoundDrawablePadding(int pad) {
2324 Drawables dr = mDrawables;
2325 if (pad == 0) {
2326 if (dr != null) {
2327 dr.mDrawablePadding = pad;
2328 }
2329 } else {
2330 if (dr == null) {
Fabrice Di Megliof7a5cdf2013-03-15 15:36:51 -07002331 mDrawables = dr = new Drawables(getContext());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002332 }
2333 dr.mDrawablePadding = pad;
2334 }
2335
2336 invalidate();
2337 requestLayout();
2338 }
2339
2340 /**
2341 * Returns the padding between the compound drawables and the text.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002342 *
2343 * @attr ref android.R.styleable#TextView_drawablePadding
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002344 */
2345 public int getCompoundDrawablePadding() {
2346 final Drawables dr = mDrawables;
2347 return dr != null ? dr.mDrawablePadding : 0;
2348 }
2349
2350 @Override
2351 public void setPadding(int left, int top, int right, int bottom) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002352 if (left != mPaddingLeft ||
2353 right != mPaddingRight ||
2354 top != mPaddingTop ||
2355 bottom != mPaddingBottom) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002356 nullLayouts();
2357 }
2358
2359 // the super call will requestLayout()
2360 super.setPadding(left, top, right, bottom);
2361 invalidate();
2362 }
2363
Fabrice Di Megliobf923eb2012-03-07 16:20:22 -08002364 @Override
2365 public void setPaddingRelative(int start, int top, int end, int bottom) {
2366 if (start != getPaddingStart() ||
2367 end != getPaddingEnd() ||
2368 top != mPaddingTop ||
2369 bottom != mPaddingBottom) {
2370 nullLayouts();
2371 }
2372
2373 // the super call will requestLayout()
2374 super.setPaddingRelative(start, top, end, bottom);
2375 invalidate();
2376 }
2377
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002378 /**
2379 * Gets the autolink mask of the text. See {@link
2380 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2381 * possible values.
2382 *
2383 * @attr ref android.R.styleable#TextView_autoLink
2384 */
2385 public final int getAutoLinkMask() {
2386 return mAutoLinkMask;
2387 }
2388
2389 /**
2390 * Sets the text color, size, style, hint color, and highlight color
2391 * from the specified TextAppearance resource.
2392 */
2393 public void setTextAppearance(Context context, int resid) {
2394 TypedArray appearance =
2395 context.obtainStyledAttributes(resid,
2396 com.android.internal.R.styleable.TextAppearance);
2397
2398 int color;
2399 ColorStateList colors;
2400 int ts;
2401
Gilles Debunne2d373a12012-04-20 15:32:19 -07002402 color = appearance.getColor(
2403 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002404 if (color != 0) {
2405 setHighlightColor(color);
2406 }
2407
2408 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2409 TextAppearance_textColor);
2410 if (colors != null) {
2411 setTextColor(colors);
2412 }
2413
2414 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2415 TextAppearance_textSize, 0);
2416 if (ts != 0) {
2417 setRawTextSize(ts);
2418 }
2419
2420 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2421 TextAppearance_textColorHint);
2422 if (colors != null) {
2423 setHintTextColor(colors);
2424 }
2425
2426 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2427 TextAppearance_textColorLink);
2428 if (colors != null) {
2429 setLinkTextColor(colors);
2430 }
2431
Raph Leviend570e892012-05-09 11:45:34 -07002432 String familyName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002433 int typefaceIndex, styleIndex;
2434
Raph Leviend570e892012-05-09 11:45:34 -07002435 familyName = appearance.getString(com.android.internal.R.styleable.
2436 TextAppearance_fontFamily);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002437 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2438 TextAppearance_typeface, -1);
2439 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2440 TextAppearance_textStyle, -1);
2441
Raph Leviend570e892012-05-09 11:45:34 -07002442 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
Gilles Debunne21078e42011-08-02 10:22:35 -07002443
Adam Powellac91df82013-02-14 13:48:47 -08002444 final int shadowcolor = appearance.getInt(
2445 com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
2446 if (shadowcolor != 0) {
2447 final float dx = appearance.getFloat(
2448 com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
2449 final float dy = appearance.getFloat(
2450 com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
2451 final float r = appearance.getFloat(
2452 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
2453
2454 setShadowLayer(r, dx, dy, shadowcolor);
2455 }
2456
Adam Powell7f8f79a2011-07-07 18:35:54 -07002457 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2458 false)) {
2459 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2460 }
2461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002462 appearance.recycle();
2463 }
2464
2465 /**
Victoria Leasedf8ef4b2012-08-17 15:34:01 -07002466 * Get the default {@link Locale} of the text in this TextView.
2467 * @return the default {@link Locale} of the text in this TextView.
2468 */
2469 public Locale getTextLocale() {
2470 return mTextPaint.getTextLocale();
2471 }
2472
2473 /**
2474 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2475 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2476 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2477 *
2478 * @param locale the {@link Locale} for drawing text, must not be null.
2479 *
2480 * @see Paint#setTextLocale
2481 */
2482 public void setTextLocale(Locale locale) {
2483 mTextPaint.setTextLocale(locale);
2484 }
2485
2486 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002487 * @return the size (in pixels) of the default text size in this TextView.
2488 */
Fabrice Di Meglioc54da1c2012-04-27 16:16:35 -07002489 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002490 public float getTextSize() {
2491 return mTextPaint.getTextSize();
2492 }
2493
2494 /**
2495 * Set the default text size to the given value, interpreted as "scaled
2496 * pixel" units. This size is adjusted based on the current density and
2497 * user font size preference.
2498 *
2499 * @param size The scaled pixel size.
2500 *
2501 * @attr ref android.R.styleable#TextView_textSize
2502 */
2503 @android.view.RemotableViewMethod
2504 public void setTextSize(float size) {
2505 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2506 }
2507
2508 /**
2509 * Set the default text size to a given unit and value. See {@link
2510 * TypedValue} for the possible dimension units.
2511 *
2512 * @param unit The desired dimension unit.
2513 * @param size The desired size in the given units.
2514 *
2515 * @attr ref android.R.styleable#TextView_textSize
2516 */
2517 public void setTextSize(int unit, float size) {
2518 Context c = getContext();
2519 Resources r;
2520
2521 if (c == null)
2522 r = Resources.getSystem();
2523 else
2524 r = c.getResources();
2525
2526 setRawTextSize(TypedValue.applyDimension(
2527 unit, size, r.getDisplayMetrics()));
2528 }
2529
2530 private void setRawTextSize(float size) {
2531 if (size != mTextPaint.getTextSize()) {
2532 mTextPaint.setTextSize(size);
2533
2534 if (mLayout != null) {
2535 nullLayouts();
2536 requestLayout();
2537 invalidate();
2538 }
2539 }
2540 }
2541
2542 /**
2543 * @return the extent by which text is currently being stretched
2544 * horizontally. This will usually be 1.
2545 */
2546 public float getTextScaleX() {
2547 return mTextPaint.getTextScaleX();
2548 }
2549
2550 /**
2551 * Sets the extent by which text should be stretched horizontally.
2552 *
2553 * @attr ref android.R.styleable#TextView_textScaleX
2554 */
2555 @android.view.RemotableViewMethod
2556 public void setTextScaleX(float size) {
2557 if (size != mTextPaint.getTextScaleX()) {
Romain Guy939151f2009-04-08 14:22:40 -07002558 mUserSetTextScaleX = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002559 mTextPaint.setTextScaleX(size);
2560
2561 if (mLayout != null) {
2562 nullLayouts();
2563 requestLayout();
2564 invalidate();
2565 }
2566 }
2567 }
2568
2569 /**
2570 * Sets the typeface and style in which the text should be displayed.
2571 * Note that not all Typeface families actually have bold and italic
2572 * variants, so you may need to use
2573 * {@link #setTypeface(Typeface, int)} to get the appearance
2574 * that you actually want.
2575 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002576 * @see #getTypeface()
2577 *
Raph Leviend570e892012-05-09 11:45:34 -07002578 * @attr ref android.R.styleable#TextView_fontFamily
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002579 * @attr ref android.R.styleable#TextView_typeface
2580 * @attr ref android.R.styleable#TextView_textStyle
2581 */
2582 public void setTypeface(Typeface tf) {
2583 if (mTextPaint.getTypeface() != tf) {
2584 mTextPaint.setTypeface(tf);
2585
2586 if (mLayout != null) {
2587 nullLayouts();
2588 requestLayout();
2589 invalidate();
2590 }
2591 }
2592 }
2593
2594 /**
2595 * @return the current typeface and style in which the text is being
2596 * displayed.
Gilles Debunnef03acef2012-04-30 19:26:19 -07002597 *
2598 * @see #setTypeface(Typeface)
2599 *
Raph Leviend570e892012-05-09 11:45:34 -07002600 * @attr ref android.R.styleable#TextView_fontFamily
Gilles Debunnef03acef2012-04-30 19:26:19 -07002601 * @attr ref android.R.styleable#TextView_typeface
2602 * @attr ref android.R.styleable#TextView_textStyle
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002603 */
2604 public Typeface getTypeface() {
2605 return mTextPaint.getTypeface();
2606 }
2607
2608 /**
2609 * Sets the text color for all the states (normal, selected,
2610 * focused) to be this color.
2611 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002612 * @see #setTextColor(ColorStateList)
2613 * @see #getTextColors()
2614 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002615 * @attr ref android.R.styleable#TextView_textColor
2616 */
2617 @android.view.RemotableViewMethod
2618 public void setTextColor(int color) {
2619 mTextColor = ColorStateList.valueOf(color);
2620 updateTextColors();
2621 }
2622
2623 /**
2624 * Sets the text color.
2625 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002626 * @see #setTextColor(int)
2627 * @see #getTextColors()
2628 * @see #setHintTextColor(ColorStateList)
2629 * @see #setLinkTextColor(ColorStateList)
2630 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002631 * @attr ref android.R.styleable#TextView_textColor
2632 */
2633 public void setTextColor(ColorStateList colors) {
2634 if (colors == null) {
2635 throw new NullPointerException();
2636 }
2637
2638 mTextColor = colors;
2639 updateTextColors();
2640 }
2641
2642 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002643 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002644 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002645 * @see #setTextColor(ColorStateList)
2646 * @see #setTextColor(int)
2647 *
2648 * @attr ref android.R.styleable#TextView_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002649 */
2650 public final ColorStateList getTextColors() {
2651 return mTextColor;
2652 }
2653
2654 /**
2655 * <p>Return the current color selected for normal text.</p>
2656 *
2657 * @return Returns the current text color.
2658 */
2659 public final int getCurrentTextColor() {
2660 return mCurTextColor;
2661 }
2662
2663 /**
2664 * Sets the color used to display the selection highlight.
2665 *
2666 * @attr ref android.R.styleable#TextView_textColorHighlight
2667 */
2668 @android.view.RemotableViewMethod
2669 public void setHighlightColor(int color) {
2670 if (mHighlightColor != color) {
2671 mHighlightColor = color;
2672 invalidate();
2673 }
2674 }
2675
2676 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002677 * @return the color used to display the selection highlight
2678 *
2679 * @see #setHighlightColor(int)
2680 *
2681 * @attr ref android.R.styleable#TextView_textColorHighlight
2682 */
2683 public int getHighlightColor() {
2684 return mHighlightColor;
2685 }
2686
2687 /**
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002688 * Sets whether the soft input method will be made visible when this
2689 * TextView gets focused. The default is true.
2690 * @hide
2691 */
2692 @android.view.RemotableViewMethod
2693 public final void setShowSoftInputOnFocus(boolean show) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07002694 createEditorIfNeeded();
Gilles Debunne3473b2b2012-04-20 16:21:10 -07002695 mEditor.mShowSoftInputOnFocus = show;
2696 }
2697
2698 /**
2699 * Returns whether the soft input method will be made visible when this
2700 * TextView gets focused. The default is true.
2701 * @hide
2702 */
2703 public final boolean getShowSoftInputOnFocus() {
2704 // When there is no Editor, return default true value
2705 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2706 }
2707
2708 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002709 * Gives the text a shadow of the specified radius and color, the specified
2710 * distance from its normal position.
2711 *
2712 * @attr ref android.R.styleable#TextView_shadowColor
2713 * @attr ref android.R.styleable#TextView_shadowDx
2714 * @attr ref android.R.styleable#TextView_shadowDy
2715 * @attr ref android.R.styleable#TextView_shadowRadius
2716 */
2717 public void setShadowLayer(float radius, float dx, float dy, int color) {
2718 mTextPaint.setShadowLayer(radius, dx, dy, color);
2719
2720 mShadowRadius = radius;
2721 mShadowDx = dx;
2722 mShadowDy = dy;
2723
Gilles Debunne33b7de852012-03-12 11:57:48 -07002724 // Will change text clip region
Gilles Debunne2d373a12012-04-20 15:32:19 -07002725 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002726 invalidate();
2727 }
2728
2729 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002730 * Gets the radius of the shadow layer.
2731 *
2732 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2733 *
2734 * @see #setShadowLayer(float, float, float, int)
2735 *
2736 * @attr ref android.R.styleable#TextView_shadowRadius
2737 */
2738 public float getShadowRadius() {
2739 return mShadowRadius;
2740 }
2741
2742 /**
2743 * @return the horizontal offset of the shadow layer
2744 *
2745 * @see #setShadowLayer(float, float, float, int)
2746 *
2747 * @attr ref android.R.styleable#TextView_shadowDx
2748 */
2749 public float getShadowDx() {
2750 return mShadowDx;
2751 }
2752
2753 /**
2754 * @return the vertical offset of the shadow layer
2755 *
2756 * @see #setShadowLayer(float, float, float, int)
2757 *
2758 * @attr ref android.R.styleable#TextView_shadowDy
2759 */
2760 public float getShadowDy() {
2761 return mShadowDy;
2762 }
2763
2764 /**
2765 * @return the color of the shadow layer
2766 *
2767 * @see #setShadowLayer(float, float, float, int)
2768 *
2769 * @attr ref android.R.styleable#TextView_shadowColor
2770 */
2771 public int getShadowColor() {
2772 return mTextPaint.shadowColor;
2773 }
2774
2775 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002776 * @return the base paint used for the text. Please use this only to
2777 * consult the Paint's properties and not to change them.
2778 */
2779 public TextPaint getPaint() {
2780 return mTextPaint;
2781 }
2782
2783 /**
2784 * Sets the autolink mask of the text. See {@link
2785 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2786 * possible values.
2787 *
2788 * @attr ref android.R.styleable#TextView_autoLink
2789 */
2790 @android.view.RemotableViewMethod
2791 public final void setAutoLinkMask(int mask) {
2792 mAutoLinkMask = mask;
2793 }
2794
2795 /**
2796 * Sets whether the movement method will automatically be set to
2797 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2798 * set to nonzero and links are detected in {@link #setText}.
2799 * The default is true.
2800 *
2801 * @attr ref android.R.styleable#TextView_linksClickable
2802 */
2803 @android.view.RemotableViewMethod
2804 public final void setLinksClickable(boolean whether) {
2805 mLinksClickable = whether;
2806 }
2807
2808 /**
2809 * Returns whether the movement method will automatically be set to
2810 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
2811 * set to nonzero and links are detected in {@link #setText}.
2812 * The default is true.
2813 *
2814 * @attr ref android.R.styleable#TextView_linksClickable
2815 */
2816 public final boolean getLinksClickable() {
2817 return mLinksClickable;
2818 }
2819
2820 /**
2821 * Returns the list of URLSpans attached to the text
2822 * (by {@link Linkify} or otherwise) if any. You can call
2823 * {@link URLSpan#getURL} on them to find where they link to
2824 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
2825 * to find the region of the text they are attached to.
2826 */
2827 public URLSpan[] getUrls() {
2828 if (mText instanceof Spanned) {
2829 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
2830 } else {
2831 return new URLSpan[0];
2832 }
2833 }
2834
2835 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002836 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
2837 * TextView.
2838 *
2839 * @see #setHintTextColor(ColorStateList)
2840 * @see #getHintTextColors()
2841 * @see #setTextColor(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002842 *
2843 * @attr ref android.R.styleable#TextView_textColorHint
2844 */
2845 @android.view.RemotableViewMethod
2846 public final void setHintTextColor(int color) {
2847 mHintTextColor = ColorStateList.valueOf(color);
2848 updateTextColors();
2849 }
2850
2851 /**
2852 * Sets the color of the hint text.
2853 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002854 * @see #getHintTextColors()
2855 * @see #setHintTextColor(int)
2856 * @see #setTextColor(ColorStateList)
2857 * @see #setLinkTextColor(ColorStateList)
2858 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002859 * @attr ref android.R.styleable#TextView_textColorHint
2860 */
2861 public final void setHintTextColor(ColorStateList colors) {
2862 mHintTextColor = colors;
2863 updateTextColors();
2864 }
2865
2866 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002867 * @return the color of the hint text, for the different states of this TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002868 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002869 * @see #setHintTextColor(ColorStateList)
2870 * @see #setHintTextColor(int)
2871 * @see #setTextColor(ColorStateList)
2872 * @see #setLinkTextColor(ColorStateList)
2873 *
2874 * @attr ref android.R.styleable#TextView_textColorHint
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002875 */
2876 public final ColorStateList getHintTextColors() {
2877 return mHintTextColor;
2878 }
2879
2880 /**
2881 * <p>Return the current color selected to paint the hint text.</p>
2882 *
2883 * @return Returns the current hint text color.
2884 */
2885 public final int getCurrentHintTextColor() {
2886 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
2887 }
2888
2889 /**
2890 * Sets the color of links in the text.
2891 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002892 * @see #setLinkTextColor(ColorStateList)
2893 * @see #getLinkTextColors()
2894 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002895 * @attr ref android.R.styleable#TextView_textColorLink
2896 */
2897 @android.view.RemotableViewMethod
2898 public final void setLinkTextColor(int color) {
2899 mLinkTextColor = ColorStateList.valueOf(color);
2900 updateTextColors();
2901 }
2902
2903 /**
2904 * Sets the color of links in the text.
2905 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002906 * @see #setLinkTextColor(int)
2907 * @see #getLinkTextColors()
2908 * @see #setTextColor(ColorStateList)
2909 * @see #setHintTextColor(ColorStateList)
2910 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002911 * @attr ref android.R.styleable#TextView_textColorLink
2912 */
2913 public final void setLinkTextColor(ColorStateList colors) {
2914 mLinkTextColor = colors;
2915 updateTextColors();
2916 }
2917
2918 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07002919 * @return the list of colors used to paint the links in the text, for the different states of
2920 * this TextView
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002921 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07002922 * @see #setLinkTextColor(ColorStateList)
2923 * @see #setLinkTextColor(int)
2924 *
2925 * @attr ref android.R.styleable#TextView_textColorLink
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002926 */
2927 public final ColorStateList getLinkTextColors() {
2928 return mLinkTextColor;
2929 }
2930
2931 /**
2932 * Sets the horizontal alignment of the text and the
2933 * vertical gravity that will be used when there is extra space
2934 * in the TextView beyond what is required for the text itself.
2935 *
2936 * @see android.view.Gravity
2937 * @attr ref android.R.styleable#TextView_gravity
2938 */
2939 public void setGravity(int gravity) {
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002940 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
Fabrice Di Meglio9e3b0022011-06-06 16:30:29 -07002941 gravity |= Gravity.START;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002942 }
2943 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
2944 gravity |= Gravity.TOP;
2945 }
2946
2947 boolean newLayout = false;
2948
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07002949 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
2950 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002951 newLayout = true;
2952 }
2953
2954 if (gravity != mGravity) {
2955 invalidate();
2956 }
2957
2958 mGravity = gravity;
2959
2960 if (mLayout != null && newLayout) {
2961 // XXX this is heavy-handed because no actual content changes.
2962 int want = mLayout.getWidth();
2963 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
2964
2965 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
2966 mRight - mLeft - getCompoundPaddingLeft() -
2967 getCompoundPaddingRight(), true);
2968 }
2969 }
2970
2971 /**
2972 * Returns the horizontal and vertical alignment of this TextView.
2973 *
2974 * @see android.view.Gravity
2975 * @attr ref android.R.styleable#TextView_gravity
2976 */
2977 public int getGravity() {
2978 return mGravity;
2979 }
2980
2981 /**
2982 * @return the flags on the Paint being used to display the text.
2983 * @see Paint#getFlags
2984 */
2985 public int getPaintFlags() {
2986 return mTextPaint.getFlags();
2987 }
2988
2989 /**
2990 * Sets flags on the Paint being used to display the text and
2991 * reflows the text if they are different from the old flags.
2992 * @see Paint#setFlags
2993 */
2994 @android.view.RemotableViewMethod
2995 public void setPaintFlags(int flags) {
2996 if (mTextPaint.getFlags() != flags) {
2997 mTextPaint.setFlags(flags);
2998
2999 if (mLayout != null) {
3000 nullLayouts();
3001 requestLayout();
3002 invalidate();
3003 }
3004 }
3005 }
3006
3007 /**
3008 * Sets whether the text should be allowed to be wider than the
3009 * View is. If false, it will be wrapped to the width of the View.
3010 *
3011 * @attr ref android.R.styleable#TextView_scrollHorizontally
3012 */
3013 public void setHorizontallyScrolling(boolean whether) {
Gilles Debunne22378292011-08-12 10:38:52 -07003014 if (mHorizontallyScrolling != whether) {
3015 mHorizontallyScrolling = whether;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003016
Gilles Debunne22378292011-08-12 10:38:52 -07003017 if (mLayout != null) {
3018 nullLayouts();
3019 requestLayout();
3020 invalidate();
3021 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003022 }
3023 }
3024
3025 /**
Gilles Debunnef2a02012011-10-27 11:10:14 -07003026 * Returns whether the text is allowed to be wider than the View is.
3027 * If false, the text will be wrapped to the width of the View.
3028 *
3029 * @attr ref android.R.styleable#TextView_scrollHorizontally
3030 * @hide
3031 */
3032 public boolean getHorizontallyScrolling() {
3033 return mHorizontallyScrolling;
3034 }
3035
3036 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003037 * Makes the TextView at least this many lines tall.
3038 *
3039 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3040 * set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003041 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003042 * @see #getMinLines()
3043 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003044 * @attr ref android.R.styleable#TextView_minLines
3045 */
3046 @android.view.RemotableViewMethod
3047 public void setMinLines(int minlines) {
3048 mMinimum = minlines;
3049 mMinMode = LINES;
3050
3051 requestLayout();
3052 invalidate();
3053 }
3054
3055 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003056 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3057 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3058 *
3059 * @see #setMinLines(int)
3060 *
3061 * @attr ref android.R.styleable#TextView_minLines
3062 */
3063 public int getMinLines() {
3064 return mMinMode == LINES ? mMinimum : -1;
3065 }
3066
3067 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003068 * Makes the TextView at least this many pixels tall.
3069 *
3070 * Setting this value overrides any other (minimum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003071 *
3072 * @attr ref android.R.styleable#TextView_minHeight
3073 */
3074 @android.view.RemotableViewMethod
3075 public void setMinHeight(int minHeight) {
3076 mMinimum = minHeight;
3077 mMinMode = PIXELS;
3078
3079 requestLayout();
3080 invalidate();
3081 }
3082
3083 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003084 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3085 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3086 *
3087 * @see #setMinHeight(int)
3088 *
3089 * @attr ref android.R.styleable#TextView_minHeight
3090 */
3091 public int getMinHeight() {
3092 return mMinMode == PIXELS ? mMinimum : -1;
3093 }
3094
3095 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003096 * Makes the TextView at most this many lines tall.
3097 *
3098 * Setting this value overrides any other (maximum) height setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003099 *
3100 * @attr ref android.R.styleable#TextView_maxLines
3101 */
3102 @android.view.RemotableViewMethod
3103 public void setMaxLines(int maxlines) {
3104 mMaximum = maxlines;
3105 mMaxMode = LINES;
3106
3107 requestLayout();
3108 invalidate();
3109 }
3110
3111 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003112 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3113 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3114 *
3115 * @see #setMaxLines(int)
3116 *
3117 * @attr ref android.R.styleable#TextView_maxLines
3118 */
3119 public int getMaxLines() {
3120 return mMaxMode == LINES ? mMaximum : -1;
3121 }
3122
3123 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003124 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3125 * {@link #setMaxLines(int)} method.
3126 *
3127 * Setting this value overrides any other (maximum) number of lines setting.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003128 *
3129 * @attr ref android.R.styleable#TextView_maxHeight
3130 */
3131 @android.view.RemotableViewMethod
3132 public void setMaxHeight(int maxHeight) {
3133 mMaximum = maxHeight;
3134 mMaxMode = PIXELS;
3135
3136 requestLayout();
3137 invalidate();
3138 }
3139
3140 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003141 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3142 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3143 *
3144 * @see #setMaxHeight(int)
3145 *
3146 * @attr ref android.R.styleable#TextView_maxHeight
3147 */
3148 public int getMaxHeight() {
3149 return mMaxMode == PIXELS ? mMaximum : -1;
3150 }
3151
3152 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003153 * Makes the TextView exactly this many lines tall.
3154 *
3155 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3156 * height setting. A single line TextView will set this value to 1.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003157 *
3158 * @attr ref android.R.styleable#TextView_lines
3159 */
3160 @android.view.RemotableViewMethod
3161 public void setLines(int lines) {
3162 mMaximum = mMinimum = lines;
3163 mMaxMode = mMinMode = LINES;
3164
3165 requestLayout();
3166 invalidate();
3167 }
3168
3169 /**
3170 * Makes the TextView exactly this many pixels tall.
3171 * You could do the same thing by specifying this number in the
3172 * LayoutParams.
3173 *
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003174 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3175 * height setting.
3176 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003177 * @attr ref android.R.styleable#TextView_height
3178 */
3179 @android.view.RemotableViewMethod
3180 public void setHeight(int pixels) {
3181 mMaximum = mMinimum = pixels;
3182 mMaxMode = mMinMode = PIXELS;
3183
3184 requestLayout();
3185 invalidate();
3186 }
3187
3188 /**
3189 * Makes the TextView at least this many ems wide
3190 *
3191 * @attr ref android.R.styleable#TextView_minEms
3192 */
3193 @android.view.RemotableViewMethod
3194 public void setMinEms(int minems) {
3195 mMinWidth = minems;
3196 mMinWidthMode = EMS;
3197
3198 requestLayout();
3199 invalidate();
3200 }
3201
3202 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003203 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3204 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3205 *
3206 * @see #setMinEms(int)
3207 * @see #setEms(int)
3208 *
3209 * @attr ref android.R.styleable#TextView_minEms
3210 */
3211 public int getMinEms() {
3212 return mMinWidthMode == EMS ? mMinWidth : -1;
3213 }
3214
3215 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003216 * Makes the TextView at least this many pixels wide
3217 *
3218 * @attr ref android.R.styleable#TextView_minWidth
3219 */
3220 @android.view.RemotableViewMethod
3221 public void setMinWidth(int minpixels) {
3222 mMinWidth = minpixels;
3223 mMinWidthMode = PIXELS;
3224
3225 requestLayout();
3226 invalidate();
3227 }
3228
3229 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003230 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3231 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3232 *
3233 * @see #setMinWidth(int)
3234 * @see #setWidth(int)
3235 *
3236 * @attr ref android.R.styleable#TextView_minWidth
3237 */
3238 public int getMinWidth() {
3239 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3240 }
3241
3242 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003243 * Makes the TextView at most this many ems wide
3244 *
3245 * @attr ref android.R.styleable#TextView_maxEms
3246 */
3247 @android.view.RemotableViewMethod
3248 public void setMaxEms(int maxems) {
3249 mMaxWidth = maxems;
3250 mMaxWidthMode = EMS;
3251
3252 requestLayout();
3253 invalidate();
3254 }
3255
3256 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003257 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3258 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3259 *
3260 * @see #setMaxEms(int)
3261 * @see #setEms(int)
3262 *
3263 * @attr ref android.R.styleable#TextView_maxEms
3264 */
3265 public int getMaxEms() {
3266 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3267 }
3268
3269 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003270 * Makes the TextView at most this many pixels wide
3271 *
3272 * @attr ref android.R.styleable#TextView_maxWidth
3273 */
3274 @android.view.RemotableViewMethod
3275 public void setMaxWidth(int maxpixels) {
3276 mMaxWidth = maxpixels;
3277 mMaxWidthMode = PIXELS;
3278
3279 requestLayout();
3280 invalidate();
3281 }
3282
3283 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003284 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3285 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3286 *
3287 * @see #setMaxWidth(int)
3288 * @see #setWidth(int)
3289 *
3290 * @attr ref android.R.styleable#TextView_maxWidth
3291 */
3292 public int getMaxWidth() {
3293 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3294 }
3295
3296 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003297 * Makes the TextView exactly this many ems wide
3298 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003299 * @see #setMaxEms(int)
3300 * @see #setMinEms(int)
3301 * @see #getMinEms()
3302 * @see #getMaxEms()
3303 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003304 * @attr ref android.R.styleable#TextView_ems
3305 */
3306 @android.view.RemotableViewMethod
3307 public void setEms(int ems) {
3308 mMaxWidth = mMinWidth = ems;
3309 mMaxWidthMode = mMinWidthMode = EMS;
3310
3311 requestLayout();
3312 invalidate();
3313 }
3314
3315 /**
3316 * Makes the TextView exactly this many pixels wide.
3317 * You could do the same thing by specifying this number in the
3318 * LayoutParams.
3319 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07003320 * @see #setMaxWidth(int)
3321 * @see #setMinWidth(int)
3322 * @see #getMinWidth()
3323 * @see #getMaxWidth()
3324 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003325 * @attr ref android.R.styleable#TextView_width
3326 */
3327 @android.view.RemotableViewMethod
3328 public void setWidth(int pixels) {
3329 mMaxWidth = mMinWidth = pixels;
3330 mMaxWidthMode = mMinWidthMode = PIXELS;
3331
3332 requestLayout();
3333 invalidate();
3334 }
3335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003336 /**
3337 * Sets line spacing for this TextView. Each line will have its height
3338 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3339 *
3340 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3341 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3342 */
3343 public void setLineSpacing(float add, float mult) {
Gilles Debunne22378292011-08-12 10:38:52 -07003344 if (mSpacingAdd != add || mSpacingMult != mult) {
3345 mSpacingAdd = add;
3346 mSpacingMult = mult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003347
Gilles Debunne22378292011-08-12 10:38:52 -07003348 if (mLayout != null) {
3349 nullLayouts();
3350 requestLayout();
3351 invalidate();
3352 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003353 }
3354 }
3355
3356 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07003357 * Gets the line spacing multiplier
3358 *
3359 * @return the value by which each line's height is multiplied to get its actual height.
3360 *
3361 * @see #setLineSpacing(float, float)
3362 * @see #getLineSpacingExtra()
3363 *
3364 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3365 */
3366 public float getLineSpacingMultiplier() {
3367 return mSpacingMult;
3368 }
3369
3370 /**
3371 * Gets the line spacing extra space
3372 *
3373 * @return the extra space that is added to the height of each lines of this TextView.
3374 *
3375 * @see #setLineSpacing(float, float)
3376 * @see #getLineSpacingMultiplier()
3377 *
3378 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3379 */
3380 public float getLineSpacingExtra() {
3381 return mSpacingAdd;
3382 }
3383
3384 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003385 * Convenience method: Append the specified text to the TextView's
3386 * display buffer, upgrading it to BufferType.EDITABLE if it was
3387 * not already editable.
3388 */
3389 public final void append(CharSequence text) {
3390 append(text, 0, text.length());
3391 }
3392
3393 /**
3394 * Convenience method: Append the specified text slice to the TextView's
3395 * display buffer, upgrading it to BufferType.EDITABLE if it was
3396 * not already editable.
3397 */
3398 public void append(CharSequence text, int start, int end) {
3399 if (!(mText instanceof Editable)) {
3400 setText(mText, BufferType.EDITABLE);
3401 }
3402
3403 ((Editable) mText).append(text, start, end);
3404 }
3405
3406 private void updateTextColors() {
3407 boolean inval = false;
3408 int color = mTextColor.getColorForState(getDrawableState(), 0);
3409 if (color != mCurTextColor) {
3410 mCurTextColor = color;
3411 inval = true;
3412 }
3413 if (mLinkTextColor != null) {
3414 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3415 if (color != mTextPaint.linkColor) {
3416 mTextPaint.linkColor = color;
3417 inval = true;
3418 }
3419 }
3420 if (mHintTextColor != null) {
3421 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3422 if (color != mCurHintTextColor && mText.length() == 0) {
3423 mCurHintTextColor = color;
3424 inval = true;
3425 }
3426 }
3427 if (inval) {
Gilles Debunne33b7de852012-03-12 11:57:48 -07003428 // Text needs to be redrawn with the new color
Gilles Debunne2d373a12012-04-20 15:32:19 -07003429 if (mEditor != null) mEditor.invalidateTextDisplayList();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003430 invalidate();
3431 }
3432 }
3433
3434 @Override
3435 protected void drawableStateChanged() {
3436 super.drawableStateChanged();
3437 if (mTextColor != null && mTextColor.isStateful()
3438 || (mHintTextColor != null && mHintTextColor.isStateful())
3439 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3440 updateTextColors();
3441 }
3442
3443 final Drawables dr = mDrawables;
3444 if (dr != null) {
3445 int[] state = getDrawableState();
3446 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3447 dr.mDrawableTop.setState(state);
3448 }
3449 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3450 dr.mDrawableBottom.setState(state);
3451 }
3452 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3453 dr.mDrawableLeft.setState(state);
3454 }
3455 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3456 dr.mDrawableRight.setState(state);
3457 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07003458 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3459 dr.mDrawableStart.setState(state);
3460 }
3461 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3462 dr.mDrawableEnd.setState(state);
3463 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003464 }
3465 }
3466
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003467 @Override
3468 public Parcelable onSaveInstanceState() {
3469 Parcelable superState = super.onSaveInstanceState();
3470
3471 // Save state if we are forced to
3472 boolean save = mFreezesText;
3473 int start = 0;
3474 int end = 0;
3475
3476 if (mText != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07003477 start = getSelectionStart();
3478 end = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003479 if (start >= 0 || end >= 0) {
3480 // Or save state if there is a selection
3481 save = true;
3482 }
3483 }
3484
3485 if (save) {
3486 SavedState ss = new SavedState(superState);
3487 // XXX Should also save the current scroll position!
3488 ss.selStart = start;
3489 ss.selEnd = end;
3490
3491 if (mText instanceof Spanned) {
3492 /*
3493 * Calling setText() strips off any ChangeWatchers;
3494 * strip them now to avoid leaking references.
3495 * But do it to a copy so that if there are any
3496 * further changes to the text of this view, it
3497 * won't get into an inconsistent state.
3498 */
3499
3500 Spannable sp = new SpannableString(mText);
3501
Gilles Debunne176cd0d2011-09-29 16:37:27 -07003502 for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003503 sp.removeSpan(cw);
3504 }
3505
Gilles Debunne60e21862012-01-30 15:04:14 -08003506 if (mEditor != null) {
3507 removeMisspelledSpans(sp);
Gilles Debunne2d373a12012-04-20 15:32:19 -07003508 sp.removeSpan(mEditor.mSuggestionRangeSpan);
Gilles Debunne60e21862012-01-30 15:04:14 -08003509 }
Gilles Debunneaa67eef2011-06-01 18:03:37 -07003510
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003511 ss.text = sp;
3512 } else {
3513 ss.text = mText.toString();
3514 }
3515
3516 if (isFocused() && start >= 0 && end >= 0) {
3517 ss.frozenWithFocus = true;
3518 }
3519
Gilles Debunne60e21862012-01-30 15:04:14 -08003520 ss.error = getError();
The Android Open Source Project4df24232009-03-05 14:34:35 -08003521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003522 return ss;
3523 }
3524
3525 return superState;
3526 }
3527
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07003528 void removeMisspelledSpans(Spannable spannable) {
3529 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3530 SuggestionSpan.class);
3531 for (int i = 0; i < suggestionSpans.length; i++) {
3532 int flags = suggestionSpans[i].getFlags();
3533 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3534 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3535 spannable.removeSpan(suggestionSpans[i]);
3536 }
3537 }
3538 }
3539
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003540 @Override
3541 public void onRestoreInstanceState(Parcelable state) {
3542 if (!(state instanceof SavedState)) {
3543 super.onRestoreInstanceState(state);
3544 return;
3545 }
3546
3547 SavedState ss = (SavedState)state;
3548 super.onRestoreInstanceState(ss.getSuperState());
3549
3550 // XXX restore buffer type too, as well as lots of other stuff
3551 if (ss.text != null) {
3552 setText(ss.text);
3553 }
3554
3555 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3556 if (mText instanceof Spannable) {
3557 int len = mText.length();
3558
3559 if (ss.selStart > len || ss.selEnd > len) {
3560 String restored = "";
3561
3562 if (ss.text != null) {
3563 restored = "(restored) ";
3564 }
3565
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07003566 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003567 "/" + ss.selEnd + " out of range for " + restored +
3568 "text " + mText);
3569 } else {
Gilles Debunnec1e79b42012-02-24 17:29:31 -08003570 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003571
3572 if (ss.frozenWithFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003573 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07003574 mEditor.mFrozenWithFocus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003575 }
3576 }
3577 }
3578 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08003579
3580 if (ss.error != null) {
Romain Guy9bc9fa12009-07-21 16:57:29 -07003581 final CharSequence error = ss.error;
3582 // Display the error later, after the first layout pass
3583 post(new Runnable() {
3584 public void run() {
3585 setError(error);
3586 }
3587 });
The Android Open Source Project4df24232009-03-05 14:34:35 -08003588 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003589 }
3590
3591 /**
3592 * Control whether this text view saves its entire text contents when
3593 * freezing to an icicle, in addition to dynamic state such as cursor
3594 * position. By default this is false, not saving the text. Set to true
3595 * if the text in the text view is not being saved somewhere else in
3596 * persistent storage (such as in a content provider) so that if the
3597 * view is later thawed the user will not lose their data.
3598 *
3599 * @param freezesText Controls whether a frozen icicle should include the
3600 * entire text data: true to include it, false to not.
3601 *
3602 * @attr ref android.R.styleable#TextView_freezesText
3603 */
3604 @android.view.RemotableViewMethod
3605 public void setFreezesText(boolean freezesText) {
3606 mFreezesText = freezesText;
3607 }
3608
3609 /**
3610 * Return whether this text view is including its entire text contents
3611 * in frozen icicles.
3612 *
3613 * @return Returns true if text is included, false if it isn't.
3614 *
3615 * @see #setFreezesText
3616 */
3617 public boolean getFreezesText() {
3618 return mFreezesText;
3619 }
3620
3621 ///////////////////////////////////////////////////////////////////////////
3622
3623 /**
3624 * Sets the Factory used to create new Editables.
3625 */
3626 public final void setEditableFactory(Editable.Factory factory) {
3627 mEditableFactory = factory;
3628 setText(mText);
3629 }
3630
3631 /**
3632 * Sets the Factory used to create new Spannables.
3633 */
3634 public final void setSpannableFactory(Spannable.Factory factory) {
3635 mSpannableFactory = factory;
3636 setText(mText);
3637 }
3638
3639 /**
3640 * Sets the string value of the TextView. TextView <em>does not</em> accept
3641 * HTML-like formatting, which you can do with text strings in XML resource files.
3642 * To style your strings, attach android.text.style.* objects to a
3643 * {@link android.text.SpannableString SpannableString}, or see the
3644 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
Gilles Debunne21078e42011-08-02 10:22:35 -07003645 * Available Resource Types</a> documentation for an example of setting
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003646 * formatted text in the XML resource file.
3647 *
3648 * @attr ref android.R.styleable#TextView_text
3649 */
3650 @android.view.RemotableViewMethod
3651 public final void setText(CharSequence text) {
3652 setText(text, mBufferType);
3653 }
3654
3655 /**
3656 * Like {@link #setText(CharSequence)},
3657 * except that the cursor position (if any) is retained in the new text.
3658 *
3659 * @param text The new text to place in the text view.
3660 *
3661 * @see #setText(CharSequence)
3662 */
3663 @android.view.RemotableViewMethod
3664 public final void setTextKeepState(CharSequence text) {
3665 setTextKeepState(text, mBufferType);
3666 }
3667
3668 /**
3669 * Sets the text that this TextView is to display (see
3670 * {@link #setText(CharSequence)}) and also sets whether it is stored
3671 * in a styleable/spannable buffer and whether it is editable.
3672 *
3673 * @attr ref android.R.styleable#TextView_text
3674 * @attr ref android.R.styleable#TextView_bufferType
3675 */
3676 public void setText(CharSequence text, BufferType type) {
3677 setText(text, type, true, 0);
3678
3679 if (mCharWrapper != null) {
3680 mCharWrapper.mChars = null;
3681 }
3682 }
3683
3684 private void setText(CharSequence text, BufferType type,
3685 boolean notifyBefore, int oldlen) {
3686 if (text == null) {
3687 text = "";
3688 }
3689
Luca Zanoline0760452011-09-08 12:03:37 +01003690 // If suggestions are not enabled, remove the suggestion spans from the text
3691 if (!isSuggestionsEnabled()) {
3692 text = removeSuggestionSpans(text);
3693 }
3694
Romain Guy939151f2009-04-08 14:22:40 -07003695 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003697 if (text instanceof Spanned &&
3698 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
Adam Powell282e3772011-08-30 16:51:11 -07003699 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3700 setHorizontalFadingEdgeEnabled(true);
3701 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3702 } else {
3703 setHorizontalFadingEdgeEnabled(false);
3704 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3705 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003706 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3707 }
3708
3709 int n = mFilters.length;
3710 for (int i = 0; i < n; i++) {
Gilles Debunnec1714022012-01-17 13:59:23 -08003711 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003712 if (out != null) {
3713 text = out;
3714 }
3715 }
3716
3717 if (notifyBefore) {
3718 if (mText != null) {
3719 oldlen = mText.length();
3720 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3721 } else {
3722 sendBeforeTextChanged("", 0, 0, text.length());
3723 }
3724 }
3725
3726 boolean needEditableForNotification = false;
3727
3728 if (mListeners != null && mListeners.size() != 0) {
3729 needEditableForNotification = true;
3730 }
3731
Gilles Debunne2d373a12012-04-20 15:32:19 -07003732 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3733 needEditableForNotification) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07003734 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003735 Editable t = mEditableFactory.newEditable(text);
3736 text = t;
3737 setFilters(t, mFilters);
3738 InputMethodManager imm = InputMethodManager.peekInstance();
3739 if (imm != null) imm.restartInput(this);
3740 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3741 text = mSpannableFactory.newSpannable(text);
3742 } else if (!(text instanceof CharWrapper)) {
3743 text = TextUtils.stringOrSpannedString(text);
3744 }
3745
3746 if (mAutoLinkMask != 0) {
3747 Spannable s2;
3748
3749 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3750 s2 = (Spannable) text;
3751 } else {
3752 s2 = mSpannableFactory.newSpannable(text);
3753 }
3754
3755 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3756 text = s2;
3757 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3758
3759 /*
3760 * We must go ahead and set the text before changing the
3761 * movement method, because setMovementMethod() may call
3762 * setText() again to try to upgrade the buffer type.
3763 */
3764 mText = text;
3765
Gilles Debunnecbcb3452010-12-17 15:31:02 -08003766 // Do not change the movement method for text that support text selection as it
3767 // would prevent an arbitrary cursor displacement.
Gilles Debunnebb588da2011-07-11 18:26:19 -07003768 if (mLinksClickable && !textCanBeSelected()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003769 setMovementMethod(LinkMovementMethod.getInstance());
3770 }
3771 }
3772 }
3773
3774 mBufferType = type;
3775 mText = text;
3776
Adam Powell7f8f79a2011-07-07 18:35:54 -07003777 if (mTransformation == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003778 mTransformed = text;
Adam Powell7f8f79a2011-07-07 18:35:54 -07003779 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003780 mTransformed = mTransformation.getTransformation(text, this);
Adam Powell7f8f79a2011-07-07 18:35:54 -07003781 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003782
3783 final int textLength = text.length();
3784
Adam Powell7f8f79a2011-07-07 18:35:54 -07003785 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003786 Spannable sp = (Spannable) text;
3787
Gilles Debunnec62589c2012-04-12 14:50:23 -07003788 // Remove any ChangeWatchers that might have come from other TextViews.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003789 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
3790 final int count = watchers.length;
Gilles Debunnec62589c2012-04-12 14:50:23 -07003791 for (int i = 0; i < count; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003792 sp.removeSpan(watchers[i]);
Gilles Debunnec62589c2012-04-12 14:50:23 -07003793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003794
Gilles Debunnec62589c2012-04-12 14:50:23 -07003795 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003796
3797 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
Gilles Debunne60e21862012-01-30 15:04:14 -08003798 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003799
Gilles Debunnec62589c2012-04-12 14:50:23 -07003800 if (mEditor != null) mEditor.addSpanWatchers(sp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003801
3802 if (mTransformation != null) {
3803 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003804 }
3805
3806 if (mMovement != null) {
3807 mMovement.initialize(this, (Spannable) text);
3808
3809 /*
3810 * Initializing the movement method will have set the
3811 * selection, so reset mSelectionMoved to keep that from
3812 * interfering with the normal on-focus selection-setting.
3813 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07003814 if (mEditor != null) mEditor.mSelectionMoved = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003815 }
3816 }
3817
3818 if (mLayout != null) {
3819 checkForRelayout();
3820 }
3821
3822 sendOnTextChanged(text, 0, oldlen, textLength);
3823 onTextChanged(text, 0, oldlen, textLength);
3824
3825 if (needEditableForNotification) {
3826 sendAfterTextChanged((Editable) text);
3827 }
Gilles Debunne05336272010-07-09 20:13:45 -07003828
Gilles Debunnebaaace52010-10-01 15:47:13 -07003829 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
Gilles Debunne2d373a12012-04-20 15:32:19 -07003830 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003831 }
3832
3833 /**
3834 * Sets the TextView to display the specified slice of the specified
3835 * char array. You must promise that you will not change the contents
3836 * of the array except for right before another call to setText(),
3837 * since the TextView has no way to know that the text
3838 * has changed and that it needs to invalidate and re-layout.
3839 */
3840 public final void setText(char[] text, int start, int len) {
3841 int oldlen = 0;
3842
3843 if (start < 0 || len < 0 || start + len > text.length) {
3844 throw new IndexOutOfBoundsException(start + ", " + len);
3845 }
3846
3847 /*
3848 * We must do the before-notification here ourselves because if
3849 * the old text is a CharWrapper we destroy it before calling
3850 * into the normal path.
3851 */
3852 if (mText != null) {
3853 oldlen = mText.length();
3854 sendBeforeTextChanged(mText, 0, oldlen, len);
3855 } else {
3856 sendBeforeTextChanged("", 0, 0, len);
3857 }
3858
3859 if (mCharWrapper == null) {
3860 mCharWrapper = new CharWrapper(text, start, len);
3861 } else {
3862 mCharWrapper.set(text, start, len);
3863 }
3864
3865 setText(mCharWrapper, mBufferType, false, oldlen);
3866 }
3867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003868 /**
3869 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
3870 * except that the cursor position (if any) is retained in the new text.
3871 *
3872 * @see #setText(CharSequence, android.widget.TextView.BufferType)
3873 */
3874 public final void setTextKeepState(CharSequence text, BufferType type) {
3875 int start = getSelectionStart();
3876 int end = getSelectionEnd();
3877 int len = text.length();
3878
3879 setText(text, type);
3880
3881 if (start >= 0 || end >= 0) {
3882 if (mText instanceof Spannable) {
3883 Selection.setSelection((Spannable) mText,
3884 Math.max(0, Math.min(start, len)),
3885 Math.max(0, Math.min(end, len)));
3886 }
3887 }
3888 }
3889
3890 @android.view.RemotableViewMethod
3891 public final void setText(int resid) {
3892 setText(getContext().getResources().getText(resid));
3893 }
3894
3895 public final void setText(int resid, BufferType type) {
3896 setText(getContext().getResources().getText(resid), type);
3897 }
3898
3899 /**
3900 * Sets the text to be displayed when the text of the TextView is empty.
3901 * Null means to use the normal empty text. The hint does not currently
3902 * participate in determining the size of the view.
3903 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003904 * @attr ref android.R.styleable#TextView_hint
3905 */
3906 @android.view.RemotableViewMethod
3907 public final void setHint(CharSequence hint) {
3908 mHint = TextUtils.stringOrSpannedString(hint);
3909
3910 if (mLayout != null) {
3911 checkForRelayout();
3912 }
3913
Romain Guy4dc4f732009-06-19 15:16:40 -07003914 if (mText.length() == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003915 invalidate();
Romain Guy4dc4f732009-06-19 15:16:40 -07003916 }
Gilles Debunne626c3162012-02-14 15:46:41 -08003917
Gilles Debunne33b7de852012-03-12 11:57:48 -07003918 // Invalidate display list if hint is currently used
Gilles Debunne60e21862012-01-30 15:04:14 -08003919 if (mEditor != null && mText.length() == 0 && mHint != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07003920 mEditor.invalidateTextDisplayList();
Gilles Debunne60e21862012-01-30 15:04:14 -08003921 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003922 }
3923
3924 /**
3925 * Sets the text to be displayed when the text of the TextView is empty,
3926 * from a resource.
3927 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003928 * @attr ref android.R.styleable#TextView_hint
3929 */
3930 @android.view.RemotableViewMethod
3931 public final void setHint(int resid) {
3932 setHint(getContext().getResources().getText(resid));
3933 }
3934
3935 /**
3936 * Returns the hint that is displayed when the text of the TextView
3937 * is empty.
3938 *
3939 * @attr ref android.R.styleable#TextView_hint
3940 */
3941 @ViewDebug.CapturedViewProperty
3942 public CharSequence getHint() {
3943 return mHint;
3944 }
3945
Gilles Debunned88876a2012-03-16 17:34:04 -07003946 boolean isSingleLine() {
3947 return mSingleLine;
3948 }
3949
Gilles Debunne3784a7f2011-07-15 13:49:38 -07003950 private static boolean isMultilineInputType(int type) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08003951 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
3952 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
3953 }
3954
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003955 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07003956 * Removes the suggestion spans.
3957 */
3958 CharSequence removeSuggestionSpans(CharSequence text) {
3959 if (text instanceof Spanned) {
3960 Spannable spannable;
3961 if (text instanceof Spannable) {
3962 spannable = (Spannable) text;
3963 } else {
3964 spannable = new SpannableString(text);
3965 text = spannable;
3966 }
3967
3968 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
3969 for (int i = 0; i < spans.length; i++) {
3970 spannable.removeSpan(spans[i]);
3971 }
3972 }
3973 return text;
3974 }
3975
3976 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08003977 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
3978 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
3979 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
3980 * then a soft keyboard will not be displayed for this text view.
3981 *
3982 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
3983 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
3984 * type.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003985 *
3986 * @see #getInputType()
3987 * @see #setRawInputType(int)
3988 * @see android.text.InputType
3989 * @attr ref android.R.styleable#TextView_inputType
3990 */
3991 public void setInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08003992 final boolean wasPassword = isPasswordInputType(getInputType());
3993 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003994 setInputType(type, false);
Bjorn Bringertad8da912009-09-17 10:47:35 +01003995 final boolean isPassword = isPasswordInputType(type);
3996 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08003997 boolean forceUpdate = false;
3998 if (isPassword) {
3999 setTransformationMethod(PasswordTransformationMethod.getInstance());
Raph Leviend570e892012-05-09 11:45:34 -07004000 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004001 } else if (isVisiblePassword) {
Amith Yamasania8c0edb2009-09-27 16:51:21 -07004002 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4003 forceUpdate = true;
4004 }
Raph Leviend570e892012-05-09 11:45:34 -07004005 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004006 } else if (wasPassword || wasVisiblePassword) {
4007 // not in password mode, clean up typeface and transformation
Raph Leviend570e892012-05-09 11:45:34 -07004008 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004009 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4010 forceUpdate = true;
4011 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004012 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004013
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004014 boolean singleLine = !isMultilineInputType(type);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004015
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004016 // We need to update the single line mode if it has changed or we
4017 // were previously in password mode.
Gilles Debunne91a08cf2010-11-08 17:34:49 -08004018 if (mSingleLine != singleLine || forceUpdate) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004019 // Change single line mode, but only change the transformation if
4020 // we are not in password mode.
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08004021 applySingleLine(singleLine, !isPassword, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004022 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004023
Luca Zanoline0760452011-09-08 12:03:37 +01004024 if (!isSuggestionsEnabled()) {
4025 mText = removeSuggestionSpans(mText);
4026 }
4027
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004028 InputMethodManager imm = InputMethodManager.peekInstance();
4029 if (imm != null) imm.restartInput(this);
4030 }
4031
Gilles Debunne0dcad2b2010-10-15 16:29:25 -07004032 /**
4033 * It would be better to rely on the input type for everything. A password inputType should have
4034 * a password transformation. We should hence use isPasswordInputType instead of this method.
4035 *
4036 * We should:
4037 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4038 * would install the correct transformation).
4039 * - Refuse the installation of a non-password transformation in setTransformation if the input
4040 * type is password.
4041 *
4042 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4043 * is useful since it matches what the user can see (obfuscated text or not).
4044 *
4045 * @return true if the current transformation method is of the password type.
4046 */
4047 private boolean hasPasswordTransformationMethod() {
4048 return mTransformation instanceof PasswordTransformationMethod;
4049 }
4050
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004051 private static boolean isPasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004052 final int variation =
4053 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004054 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004055 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4056 || variation
Ken Wakasa82d731a2010-12-24 23:42:41 +09004057 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4058 || variation
4059 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004060 }
4061
Gilles Debunne3784a7f2011-07-15 13:49:38 -07004062 private static boolean isVisiblePasswordInputType(int inputType) {
Gilles Debunned7483bf2010-11-10 10:47:45 -08004063 final int variation =
4064 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004065 return variation
Gilles Debunned7483bf2010-11-10 10:47:45 -08004066 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
Bjorn Bringertad8da912009-09-17 10:47:35 +01004067 }
4068
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004069 /**
4070 * Directly change the content type integer of the text view, without
4071 * modifying any other state.
4072 * @see #setInputType(int)
4073 * @see android.text.InputType
4074 * @attr ref android.R.styleable#TextView_inputType
4075 */
4076 public void setRawInputType(int type) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004077 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
Gilles Debunne5fae9962012-05-08 14:53:20 -07004078 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004079 mEditor.mInputType = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004080 }
4081
4082 private void setInputType(int type, boolean direct) {
4083 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4084 KeyListener input;
4085 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
Gilles Debunnee67b58a2010-08-31 15:55:31 -07004086 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004087 TextKeyListener.Capitalize cap;
4088 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4089 cap = TextKeyListener.Capitalize.CHARACTERS;
4090 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4091 cap = TextKeyListener.Capitalize.WORDS;
4092 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4093 cap = TextKeyListener.Capitalize.SENTENCES;
4094 } else {
4095 cap = TextKeyListener.Capitalize.NONE;
4096 }
4097 input = TextKeyListener.getInstance(autotext, cap);
4098 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4099 input = DigitsKeyListener.getInstance(
4100 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4101 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4102 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4103 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4104 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4105 input = DateKeyListener.getInstance();
4106 break;
4107 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4108 input = TimeKeyListener.getInstance();
4109 break;
4110 default:
4111 input = DateTimeKeyListener.getInstance();
4112 break;
4113 }
4114 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4115 input = DialerKeyListener.getInstance();
4116 } else {
4117 input = TextKeyListener.getInstance();
4118 }
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07004119 setRawInputType(type);
Gilles Debunne60e21862012-01-30 15:04:14 -08004120 if (direct) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004121 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004122 mEditor.mKeyListener = input;
Gilles Debunne60e21862012-01-30 15:04:14 -08004123 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004124 setKeyListenerOnly(input);
4125 }
4126 }
4127
4128 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08004129 * Get the type of the editable content.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004130 *
4131 * @see #setInputType(int)
4132 * @see android.text.InputType
4133 */
4134 public int getInputType() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004135 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004136 }
4137
4138 /**
4139 * Change the editor type integer associated with the text view, which
4140 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4141 * has focus.
4142 * @see #getImeOptions
4143 * @see android.view.inputmethod.EditorInfo
4144 * @attr ref android.R.styleable#TextView_imeOptions
4145 */
4146 public void setImeOptions(int imeOptions) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004147 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004148 mEditor.createInputContentTypeIfNeeded();
4149 mEditor.mInputContentType.imeOptions = imeOptions;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004150 }
4151
4152 /**
4153 * Get the type of the IME editor.
4154 *
4155 * @see #setImeOptions(int)
4156 * @see android.view.inputmethod.EditorInfo
4157 */
4158 public int getImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004159 return mEditor != null && mEditor.mInputContentType != null
4160 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004161 }
4162
4163 /**
4164 * Change the custom IME action associated with the text view, which
4165 * will be reported to an IME with {@link EditorInfo#actionLabel}
4166 * and {@link EditorInfo#actionId} when it has focus.
4167 * @see #getImeActionLabel
4168 * @see #getImeActionId
4169 * @see android.view.inputmethod.EditorInfo
4170 * @attr ref android.R.styleable#TextView_imeActionLabel
4171 * @attr ref android.R.styleable#TextView_imeActionId
4172 */
4173 public void setImeActionLabel(CharSequence label, int actionId) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004174 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004175 mEditor.createInputContentTypeIfNeeded();
4176 mEditor.mInputContentType.imeActionLabel = label;
4177 mEditor.mInputContentType.imeActionId = actionId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004178 }
4179
4180 /**
4181 * Get the IME action label previous set with {@link #setImeActionLabel}.
4182 *
4183 * @see #setImeActionLabel
4184 * @see android.view.inputmethod.EditorInfo
4185 */
4186 public CharSequence getImeActionLabel() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004187 return mEditor != null && mEditor.mInputContentType != null
4188 ? mEditor.mInputContentType.imeActionLabel : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004189 }
4190
4191 /**
4192 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4193 *
4194 * @see #setImeActionLabel
4195 * @see android.view.inputmethod.EditorInfo
4196 */
4197 public int getImeActionId() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004198 return mEditor != null && mEditor.mInputContentType != null
4199 ? mEditor.mInputContentType.imeActionId : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004200 }
4201
4202 /**
4203 * Set a special listener to be called when an action is performed
4204 * on the text view. This will be called when the enter key is pressed,
4205 * or when an action supplied to the IME is selected by the user. Setting
4206 * this means that the normal hard key event will not insert a newline
4207 * into the text view, even if it is multi-line; holding down the ALT
4208 * modifier will, however, allow the user to insert a newline character.
4209 */
4210 public void setOnEditorActionListener(OnEditorActionListener l) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004211 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004212 mEditor.createInputContentTypeIfNeeded();
4213 mEditor.mInputContentType.onEditorActionListener = l;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004214 }
Gilles Debunne60e21862012-01-30 15:04:14 -08004215
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004216 /**
4217 * Called when an attached input method calls
4218 * {@link InputConnection#performEditorAction(int)
4219 * InputConnection.performEditorAction()}
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004220 * for this text view. The default implementation will call your action
4221 * listener supplied to {@link #setOnEditorActionListener}, or perform
4222 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004223 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4224 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004225 * EditorInfo.IME_ACTION_DONE}.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004226 *
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004227 * <p>For backwards compatibility, if no IME options have been set and the
4228 * text view would not normally advance focus on enter, then
4229 * the NEXT and DONE actions received here will be turned into an enter
4230 * key down/up pair to go through the normal key handling.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004231 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004232 * @param actionCode The code of the action being performed.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004233 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004234 * @see #setOnEditorActionListener
4235 */
4236 public void onEditorAction(int actionCode) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004237 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004238 if (ict != null) {
4239 if (ict.onEditorActionListener != null) {
4240 if (ict.onEditorActionListener.onEditorAction(this,
4241 actionCode, null)) {
4242 return;
4243 }
4244 }
Gilles Debunne64794482011-11-30 15:45:28 -08004245
The Android Open Source Project4df24232009-03-05 14:34:35 -08004246 // This is the handling for some default action.
4247 // Note that for backwards compatibility we don't do this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004248 // default handling if explicit ime options have not been given,
The Android Open Source Project10592532009-03-18 17:39:46 -07004249 // instead turning this into the normal enter key codes that an
The Android Open Source Project4df24232009-03-05 14:34:35 -08004250 // app may be expecting.
4251 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004252 View v = focusSearch(FOCUS_FORWARD);
The Android Open Source Project4df24232009-03-05 14:34:35 -08004253 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004254 if (!v.requestFocus(FOCUS_FORWARD)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004255 throw new IllegalStateException("focus search returned a view " +
4256 "that wasn't able to take focus!");
4257 }
4258 }
4259 return;
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004260
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004261 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004262 View v = focusSearch(FOCUS_BACKWARD);
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004263 if (v != null) {
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004264 if (!v.requestFocus(FOCUS_BACKWARD)) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07004265 throw new IllegalStateException("focus search returned a view " +
4266 "that wasn't able to take focus!");
4267 }
4268 }
4269 return;
4270
The Android Open Source Project4df24232009-03-05 14:34:35 -08004271 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4272 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08004273 if (imm != null && imm.isActive(this)) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08004274 imm.hideSoftInputFromWindow(getWindowToken(), 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004275 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07004276 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004277 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004278 }
Svetoslav Ganova53efe92011-09-08 18:08:36 -07004279
Jeff Browna175a5b2012-02-15 19:18:31 -08004280 ViewRootImpl viewRootImpl = getViewRootImpl();
4281 if (viewRootImpl != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07004282 long eventTime = SystemClock.uptimeMillis();
Jeff Browna175a5b2012-02-15 19:18:31 -08004283 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004284 new KeyEvent(eventTime, eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004285 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4286 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004287 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004288 | KeyEvent.FLAG_EDITOR_ACTION));
4289 viewRootImpl.dispatchKeyFromIme(
The Android Open Source Project10592532009-03-18 17:39:46 -07004290 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
Jeff Brown6b53e8d2010-11-10 16:03:06 -08004291 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4292 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
The Android Open Source Project10592532009-03-18 17:39:46 -07004293 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
Jeff Browna175a5b2012-02-15 19:18:31 -08004294 | KeyEvent.FLAG_EDITOR_ACTION));
The Android Open Source Project10592532009-03-18 17:39:46 -07004295 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004296 }
Gilles Debunne64794482011-11-30 15:45:28 -08004297
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004298 /**
4299 * Set the private content type of the text, which is the
4300 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4301 * field that will be filled in when creating an input connection.
4302 *
4303 * @see #getPrivateImeOptions()
4304 * @see EditorInfo#privateImeOptions
4305 * @attr ref android.R.styleable#TextView_privateImeOptions
4306 */
4307 public void setPrivateImeOptions(String type) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004308 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004309 mEditor.createInputContentTypeIfNeeded();
4310 mEditor.mInputContentType.privateImeOptions = type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004311 }
4312
4313 /**
4314 * Get the private type of the content.
4315 *
4316 * @see #setPrivateImeOptions(String)
4317 * @see EditorInfo#privateImeOptions
4318 */
4319 public String getPrivateImeOptions() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004320 return mEditor != null && mEditor.mInputContentType != null
4321 ? mEditor.mInputContentType.privateImeOptions : null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004322 }
4323
4324 /**
4325 * Set the extra input data of the text, which is the
4326 * {@link EditorInfo#extras TextBoxAttribute.extras}
4327 * Bundle that will be filled in when creating an input connection. The
4328 * given integer is the resource ID of an XML resource holding an
4329 * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree.
4330 *
Gilles Debunne2d373a12012-04-20 15:32:19 -07004331 * @see #getInputExtras(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004332 * @see EditorInfo#extras
4333 * @attr ref android.R.styleable#TextView_editorExtras
4334 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004335 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004336 createEditorIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004337 XmlResourceParser parser = getResources().getXml(xmlResId);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004338 mEditor.createInputContentTypeIfNeeded();
4339 mEditor.mInputContentType.extras = new Bundle();
4340 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004341 }
4342
4343 /**
4344 * Retrieve the input extras currently associated with the text view, which
4345 * can be viewed as well as modified.
4346 *
4347 * @param create If true, the extras will be created if they don't already
4348 * exist. Otherwise, null will be returned if none have been created.
Gilles Debunnee15b3582010-06-16 15:17:21 -07004349 * @see #setInputExtras(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004350 * @see EditorInfo#extras
4351 * @attr ref android.R.styleable#TextView_editorExtras
4352 */
4353 public Bundle getInputExtras(boolean create) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004354 if (mEditor == null && !create) return null;
Gilles Debunne5fae9962012-05-08 14:53:20 -07004355 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004356 if (mEditor.mInputContentType == 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.createInputContentTypeIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004359 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004360 if (mEditor.mInputContentType.extras == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004361 if (!create) return null;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004362 mEditor.mInputContentType.extras = new Bundle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004363 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07004364 return mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004365 }
4366
4367 /**
4368 * Returns the error message that was set to be displayed with
4369 * {@link #setError}, or <code>null</code> if no error was set
4370 * or if it the error was cleared by the widget after user input.
4371 */
4372 public CharSequence getError() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004373 return mEditor == null ? null : mEditor.mError;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004374 }
4375
4376 /**
4377 * Sets the right-hand compound drawable of the TextView to the "error"
4378 * icon and sets an error message that will be displayed in a popup when
4379 * the TextView has focus. The icon and error message will be reset to
4380 * null when any key events cause changes to the TextView's text. If the
4381 * <code>error</code> is <code>null</code>, the error message and icon
4382 * will be cleared.
4383 */
4384 @android.view.RemotableViewMethod
4385 public void setError(CharSequence error) {
4386 if (error == null) {
4387 setError(null, null);
4388 } else {
4389 Drawable dr = getContext().getResources().
Gilles Debunnea85467b2011-01-19 16:53:31 -08004390 getDrawable(com.android.internal.R.drawable.indicator_input_error);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004391
4392 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4393 setError(error, dr);
4394 }
4395 }
4396
4397 /**
4398 * Sets the right-hand compound drawable of the TextView to the specified
4399 * icon and sets an error message that will be displayed in a popup when
4400 * the TextView has focus. The icon and error message will be reset to
4401 * null when any key events cause changes to the TextView's text. The
4402 * drawable must already have had {@link Drawable#setBounds} set on it.
4403 * If the <code>error</code> is <code>null</code>, the error message will
4404 * be cleared (and you should provide a <code>null</code> icon as well).
4405 */
4406 public void setError(CharSequence error, Drawable icon) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07004407 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004408 mEditor.setError(error, icon);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004409 }
4410
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004411 @Override
4412 protected boolean setFrame(int l, int t, int r, int b) {
4413 boolean result = super.setFrame(l, t, r, b);
4414
Gilles Debunne2d373a12012-04-20 15:32:19 -07004415 if (mEditor != null) mEditor.setFrame();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004416
Romain Guy986003d2009-03-25 17:42:35 -07004417 restartMarqueeIfNeeded();
4418
4419 return result;
4420 }
4421
4422 private void restartMarqueeIfNeeded() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004423 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4424 mRestartMarquee = false;
4425 startMarquee();
4426 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004427 }
4428
4429 /**
4430 * Sets the list of input filters that will be used if the buffer is
Gilles Debunne60e21862012-01-30 15:04:14 -08004431 * Editable. Has no effect otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004432 *
4433 * @attr ref android.R.styleable#TextView_maxLength
4434 */
4435 public void setFilters(InputFilter[] filters) {
4436 if (filters == null) {
4437 throw new IllegalArgumentException();
4438 }
4439
4440 mFilters = filters;
4441
4442 if (mText instanceof Editable) {
4443 setFilters((Editable) mText, filters);
4444 }
4445 }
4446
4447 /**
4448 * Sets the list of input filters on the specified Editable,
4449 * and includes mInput in the list if it is an InputFilter.
4450 */
4451 private void setFilters(Editable e, InputFilter[] filters) {
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004452 if (mEditor != null) {
4453 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4454 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4455 int num = 0;
4456 if (undoFilter) num++;
4457 if (keyFilter) num++;
4458 if (num > 0) {
4459 InputFilter[] nf = new InputFilter[filters.length + num];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004460
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004461 System.arraycopy(filters, 0, nf, 0, filters.length);
4462 num = 0;
4463 if (undoFilter) {
4464 nf[filters.length] = mEditor.mUndoInputFilter;
4465 num++;
4466 }
4467 if (keyFilter) {
4468 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4469 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004470
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004471 e.setFilters(nf);
4472 return;
4473 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004474 }
Dianne Hackborn3aa49b62013-04-26 16:39:17 -07004475 e.setFilters(filters);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004476 }
4477
4478 /**
4479 * Returns the current list of input filters.
Gilles Debunnef03acef2012-04-30 19:26:19 -07004480 *
4481 * @attr ref android.R.styleable#TextView_maxLength
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004482 */
4483 public InputFilter[] getFilters() {
4484 return mFilters;
4485 }
4486
4487 /////////////////////////////////////////////////////////////////////////
4488
Philip Milne7b757812012-09-19 18:13:44 -07004489 private int getBoxHeight(Layout l) {
4490 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4491 int padding = (l == mHintLayout) ?
4492 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4493 getExtendedPaddingTop() + getExtendedPaddingBottom();
4494 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4495 }
4496
Gilles Debunned88876a2012-03-16 17:34:04 -07004497 int getVerticalOffset(boolean forceNormal) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004498 int voffset = 0;
4499 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4500
4501 Layout l = mLayout;
4502 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4503 l = mHintLayout;
4504 }
4505
4506 if (gravity != Gravity.TOP) {
Philip Milne7b757812012-09-19 18:13:44 -07004507 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004508 int textht = l.getHeight();
4509
4510 if (textht < boxht) {
4511 if (gravity == Gravity.BOTTOM)
4512 voffset = boxht - textht;
4513 else // (gravity == Gravity.CENTER_VERTICAL)
4514 voffset = (boxht - textht) >> 1;
4515 }
4516 }
4517 return voffset;
4518 }
4519
4520 private int getBottomVerticalOffset(boolean forceNormal) {
4521 int voffset = 0;
4522 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4523
4524 Layout l = mLayout;
4525 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4526 l = mHintLayout;
4527 }
4528
4529 if (gravity != Gravity.BOTTOM) {
Philip Milne7b757812012-09-19 18:13:44 -07004530 int boxht = getBoxHeight(l);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004531 int textht = l.getHeight();
4532
4533 if (textht < boxht) {
4534 if (gravity == Gravity.TOP)
4535 voffset = boxht - textht;
4536 else // (gravity == Gravity.CENTER_VERTICAL)
4537 voffset = (boxht - textht) >> 1;
4538 }
4539 }
4540 return voffset;
4541 }
4542
Gilles Debunned88876a2012-03-16 17:34:04 -07004543 void invalidateCursorPath() {
Gilles Debunne83051b82012-02-24 20:01:13 -08004544 if (mHighlightPathBogus) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004545 invalidateCursor();
4546 } else {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004547 final int horizontalPadding = getCompoundPaddingLeft();
4548 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004549
Gilles Debunne2d373a12012-04-20 15:32:19 -07004550 if (mEditor.mCursorCount == 0) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004551 synchronized (TEMP_RECTF) {
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004552 /*
4553 * The reason for this concern about the thickness of the
4554 * cursor and doing the floor/ceil on the coordinates is that
4555 * some EditTexts (notably textfields in the Browser) have
4556 * anti-aliased text where not all the characters are
4557 * necessarily at integer-multiple locations. This should
4558 * make sure the entire cursor gets invalidated instead of
4559 * sometimes missing half a pixel.
4560 */
4561 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4562 if (thick < 1.0f) {
4563 thick = 1.0f;
4564 }
4565
4566 thick /= 2.0f;
4567
Gilles Debunne83051b82012-02-24 20:01:13 -08004568 // mHighlightPath is guaranteed to be non null at that point.
4569 mHighlightPath.computeBounds(TEMP_RECTF, false);
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004570
Gilles Debunne60e21862012-01-30 15:04:14 -08004571 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4572 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4573 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4574 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004575 }
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004576 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004577 for (int i = 0; i < mEditor.mCursorCount; i++) {
4578 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004579 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4580 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4581 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004582 }
4583 }
4584 }
4585
Gilles Debunned88876a2012-03-16 17:34:04 -07004586 void invalidateCursor() {
Gilles Debunne05336272010-07-09 20:13:45 -07004587 int where = getSelectionEnd();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004588
4589 invalidateCursor(where, where, where);
4590 }
4591
4592 private void invalidateCursor(int a, int b, int c) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004593 if (a >= 0 || b >= 0 || c >= 0) {
4594 int start = Math.min(Math.min(a, b), c);
4595 int end = Math.max(Math.max(a, b), c);
Gilles Debunne961ebb92011-12-12 10:16:04 -08004596 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004597 }
4598 }
4599
4600 /**
4601 * Invalidates the region of text enclosed between the start and end text offsets.
Gilles Debunne8615ac92011-11-29 15:25:03 -08004602 */
Gilles Debunne961ebb92011-12-12 10:16:04 -08004603 void invalidateRegion(int start, int end, boolean invalidateCursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004604 if (mLayout == null) {
4605 invalidate();
4606 } else {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004607 int lineStart = mLayout.getLineForOffset(start);
4608 int top = mLayout.getLineTop(lineStart);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004609
4610 // This is ridiculous, but the descent from the line above
4611 // can hang down into the line we really want to redraw,
4612 // so we have to invalidate part of the line above to make
4613 // sure everything that needs to be redrawn really is.
4614 // (But not the whole line above, because that would cause
4615 // the same problem with the descenders on the line above it!)
Gilles Debunne8615ac92011-11-29 15:25:03 -08004616 if (lineStart > 0) {
4617 top -= mLayout.getLineDescent(lineStart - 1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004618 }
4619
Gilles Debunne8615ac92011-11-29 15:25:03 -08004620 int lineEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004621
Gilles Debunne8615ac92011-11-29 15:25:03 -08004622 if (start == end)
4623 lineEnd = lineStart;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004624 else
Gilles Debunne8615ac92011-11-29 15:25:03 -08004625 lineEnd = mLayout.getLineForOffset(end);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004626
Gilles Debunne8615ac92011-11-29 15:25:03 -08004627 int bottom = mLayout.getLineBottom(lineEnd);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004628
Gilles Debunne83051b82012-02-24 20:01:13 -08004629 // mEditor can be null in case selection is set programmatically.
4630 if (invalidateCursor && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004631 for (int i = 0; i < mEditor.mCursorCount; i++) {
4632 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
Gilles Debunne961ebb92011-12-12 10:16:04 -08004633 top = Math.min(top, bounds.top);
4634 bottom = Math.max(bottom, bounds.bottom);
4635 }
4636 }
4637
Gilles Debunne8615ac92011-11-29 15:25:03 -08004638 final int compoundPaddingLeft = getCompoundPaddingLeft();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004639 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
Gilles Debunne8615ac92011-11-29 15:25:03 -08004640
4641 int left, right;
Gilles Debunne961ebb92011-12-12 10:16:04 -08004642 if (lineStart == lineEnd && !invalidateCursor) {
Gilles Debunne8615ac92011-11-29 15:25:03 -08004643 left = (int) mLayout.getPrimaryHorizontal(start);
4644 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4645 left += compoundPaddingLeft;
4646 right += compoundPaddingLeft;
4647 } else {
4648 // Rectangle bounding box when the region spans several lines
4649 left = compoundPaddingLeft;
4650 right = getWidth() - getCompoundPaddingRight();
Gilles Debunnef75c97e2011-02-10 16:09:53 -08004651 }
4652
Gilles Debunne8615ac92011-11-29 15:25:03 -08004653 invalidate(mScrollX + left, verticalPadding + top,
4654 mScrollX + right, verticalPadding + bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004655 }
4656 }
4657
4658 private void registerForPreDraw() {
Gilles Debunne2e37d622012-01-27 13:54:00 -08004659 if (!mPreDrawRegistered) {
4660 getViewTreeObserver().addOnPreDrawListener(this);
4661 mPreDrawRegistered = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004662 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004663 }
4664
4665 /**
4666 * {@inheritDoc}
4667 */
4668 public boolean onPreDraw() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004669 if (mLayout == null) {
4670 assumeLayout();
4671 }
4672
4673 boolean changed = false;
4674
4675 if (mMovement != null) {
Gilles Debunne05336272010-07-09 20:13:45 -07004676 /* This code also provides auto-scrolling when a cursor is moved using a
4677 * CursorController (insertion point or selection limits).
4678 * For selection, ensure start or end is visible depending on controller's state.
4679 */
4680 int curs = getSelectionEnd();
Gilles Debunnee587d832010-11-23 20:20:11 -08004681 // Do not create the controller if it is not already created.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004682 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4683 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07004684 curs = getSelectionStart();
Gilles Debunne05336272010-07-09 20:13:45 -07004685 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004686
4687 /*
4688 * TODO: This should really only keep the end in view if
4689 * it already was before the text changed. I'm not sure
4690 * of a good way to tell from here if it was.
4691 */
Gilles Debunne60e21862012-01-30 15:04:14 -08004692 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004693 curs = mText.length();
4694 }
4695
4696 if (curs >= 0) {
4697 changed = bringPointIntoView(curs);
4698 }
4699 } else {
4700 changed = bringTextIntoView();
4701 }
4702
Gilles Debunne64e54a62010-09-07 19:07:17 -07004703 // This has to be checked here since:
4704 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4705 // a screen rotation) since layout is not yet initialized at that point.
Gilles Debunne2d373a12012-04-20 15:32:19 -07004706 if (mEditor != null && mEditor.mCreatedWithASelection) {
4707 mEditor.startSelectionActionMode();
4708 mEditor.mCreatedWithASelection = false;
Gilles Debunnec01f3fe2010-12-22 17:07:36 -08004709 }
4710
4711 // Phone specific code (there is no ExtractEditText on tablets).
4712 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4713 // not be set. Do the test here instead.
Gilles Debunned88876a2012-03-16 17:34:04 -07004714 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004715 mEditor.startSelectionActionMode();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07004716 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07004717
Gilles Debunne2e37d622012-01-27 13:54:00 -08004718 getViewTreeObserver().removeOnPreDrawListener(this);
4719 mPreDrawRegistered = false;
4720
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004721 return !changed;
4722 }
4723
4724 @Override
4725 protected void onAttachedToWindow() {
4726 super.onAttachedToWindow();
4727
4728 mTemporaryDetach = false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07004729
Gilles Debunne2d373a12012-04-20 15:32:19 -07004730 if (mEditor != null) mEditor.onAttachedToWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004731 }
4732
4733 @Override
4734 protected void onDetachedFromWindow() {
4735 super.onDetachedFromWindow();
4736
Gilles Debunne2e37d622012-01-27 13:54:00 -08004737 if (mPreDrawRegistered) {
4738 getViewTreeObserver().removeOnPreDrawListener(this);
4739 mPreDrawRegistered = false;
Gilles Debunne81f08082011-02-17 14:07:19 -08004740 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004741
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004742 resetResolvedDrawables();
Gilles Debunne186aaf92011-09-16 14:26:12 -07004743
Gilles Debunne2d373a12012-04-20 15:32:19 -07004744 if (mEditor != null) mEditor.onDetachedFromWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004745 }
4746
4747 @Override
Romain Guybb9908b2012-03-08 11:14:07 -08004748 public void onScreenStateChanged(int screenState) {
4749 super.onScreenStateChanged(screenState);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004750 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
Romain Guybb9908b2012-03-08 11:14:07 -08004751 }
4752
4753 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004754 protected boolean isPaddingOffsetRequired() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004755 return mShadowRadius != 0 || mDrawables != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004756 }
4757
4758 @Override
4759 protected int getLeftPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004760 return getCompoundPaddingLeft() - mPaddingLeft +
4761 (int) Math.min(0, mShadowDx - mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004762 }
4763
4764 @Override
4765 protected int getTopPaddingOffset() {
4766 return (int) Math.min(0, mShadowDy - mShadowRadius);
4767 }
4768
4769 @Override
4770 protected int getBottomPaddingOffset() {
4771 return (int) Math.max(0, mShadowDy + mShadowRadius);
4772 }
4773
4774 @Override
4775 protected int getRightPaddingOffset() {
Romain Guy076dc9f2009-06-24 17:17:51 -07004776 return -(getCompoundPaddingRight() - mPaddingRight) +
4777 (int) Math.max(0, mShadowDx + mShadowRadius);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004778 }
4779
4780 @Override
4781 protected boolean verifyDrawable(Drawable who) {
4782 final boolean verified = super.verifyDrawable(who);
4783 if (!verified && mDrawables != null) {
4784 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004785 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
4786 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004787 }
4788 return verified;
4789 }
4790
4791 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -07004792 public void jumpDrawablesToCurrentState() {
4793 super.jumpDrawablesToCurrentState();
4794 if (mDrawables != null) {
4795 if (mDrawables.mDrawableLeft != null) {
4796 mDrawables.mDrawableLeft.jumpToCurrentState();
4797 }
4798 if (mDrawables.mDrawableTop != null) {
4799 mDrawables.mDrawableTop.jumpToCurrentState();
4800 }
4801 if (mDrawables.mDrawableRight != null) {
4802 mDrawables.mDrawableRight.jumpToCurrentState();
4803 }
4804 if (mDrawables.mDrawableBottom != null) {
4805 mDrawables.mDrawableBottom.jumpToCurrentState();
4806 }
Fabrice Di Meglioa3b6b952011-06-29 16:44:43 -07004807 if (mDrawables.mDrawableStart != null) {
4808 mDrawables.mDrawableStart.jumpToCurrentState();
4809 }
4810 if (mDrawables.mDrawableEnd != null) {
4811 mDrawables.mDrawableEnd.jumpToCurrentState();
4812 }
Dianne Hackborne2136772010-11-04 15:08:59 -07004813 }
4814 }
4815
4816 @Override
Romain Guy3c77d392009-05-20 11:26:50 -07004817 public void invalidateDrawable(Drawable drawable) {
4818 if (verifyDrawable(drawable)) {
4819 final Rect dirty = drawable.getBounds();
4820 int scrollX = mScrollX;
4821 int scrollY = mScrollY;
4822
4823 // IMPORTANT: The coordinates below are based on the coordinates computed
4824 // for each compound drawable in onDraw(). Make sure to update each section
4825 // accordingly.
4826 final TextView.Drawables drawables = mDrawables;
Romain Guya6cd4e02009-05-20 15:09:21 -07004827 if (drawables != null) {
4828 if (drawable == drawables.mDrawableLeft) {
4829 final int compoundPaddingTop = getCompoundPaddingTop();
4830 final int compoundPaddingBottom = getCompoundPaddingBottom();
4831 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004832
Romain Guya6cd4e02009-05-20 15:09:21 -07004833 scrollX += mPaddingLeft;
4834 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
4835 } else if (drawable == drawables.mDrawableRight) {
4836 final int compoundPaddingTop = getCompoundPaddingTop();
4837 final int compoundPaddingBottom = getCompoundPaddingBottom();
4838 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
Romain Guy3c77d392009-05-20 11:26:50 -07004839
Romain Guya6cd4e02009-05-20 15:09:21 -07004840 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
4841 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
4842 } else if (drawable == drawables.mDrawableTop) {
4843 final int compoundPaddingLeft = getCompoundPaddingLeft();
4844 final int compoundPaddingRight = getCompoundPaddingRight();
4845 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004846
Romain Guya6cd4e02009-05-20 15:09:21 -07004847 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
4848 scrollY += mPaddingTop;
4849 } else if (drawable == drawables.mDrawableBottom) {
4850 final int compoundPaddingLeft = getCompoundPaddingLeft();
4851 final int compoundPaddingRight = getCompoundPaddingRight();
4852 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
Romain Guy3c77d392009-05-20 11:26:50 -07004853
Romain Guya6cd4e02009-05-20 15:09:21 -07004854 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
4855 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
4856 }
Romain Guy3c77d392009-05-20 11:26:50 -07004857 }
4858
4859 invalidate(dirty.left + scrollX, dirty.top + scrollY,
4860 dirty.right + scrollX, dirty.bottom + scrollY);
4861 }
4862 }
4863
4864 @Override
Chet Haasedb8c9a62012-03-21 18:54:18 -07004865 public boolean hasOverlappingRendering() {
Michael Jurka0931a852013-03-21 16:07:45 +01004866 return ((getBackground() != null && getBackground().getCurrent() != null)
4867 || mText instanceof Spannable || hasSelection());
Chet Haasedb8c9a62012-03-21 18:54:18 -07004868 }
4869
Gilles Debunne86b9c782010-11-11 10:43:48 -08004870 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08004871 *
Joe Malin10d96952013-05-29 17:49:09 -07004872 * Returns the state of the {@code textIsSelectable} flag (See
4873 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
4874 * to allow users to select and copy text in a non-editable TextView, the content of an
4875 * {@link EditText} can always be selected, independently of the value of this flag.
4876 * <p>
Gilles Debunne86b9c782010-11-11 10:43:48 -08004877 *
4878 * @return True if the text displayed in this TextView can be selected by the user.
4879 *
4880 * @attr ref android.R.styleable#TextView_textIsSelectable
4881 */
4882 public boolean isTextSelectable() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004883 return mEditor == null ? false : mEditor.mTextIsSelectable;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004884 }
4885
4886 /**
Joe Malin10d96952013-05-29 17:49:09 -07004887 * Sets whether the content of this view is selectable by the user. The default is
4888 * {@code false}, meaning that the content is not selectable.
4889 * <p>
4890 * When you use a TextView to display a useful piece of information to the user (such as a
4891 * contact's address), make it selectable, so that the user can select and copy its
4892 * content. You can also use set the XML attribute
4893 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
4894 * <p>
4895 * When you call this method to set the value of {@code textIsSelectable}, it sets
4896 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
4897 * and {@code longClickable} to the same value. These flags correspond to the attributes
4898 * {@link android.R.styleable#View_focusable android:focusable},
4899 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
4900 * {@link android.R.styleable#View_clickable android:clickable}, and
4901 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
4902 * flags to a state you had set previously, call one or more of the following methods:
4903 * {@link #setFocusable(boolean) setFocusable()},
4904 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
4905 * {@link #setClickable(boolean) setClickable()} or
4906 * {@link #setLongClickable(boolean) setLongClickable()}.
Gilles Debunne60e21862012-01-30 15:04:14 -08004907 *
Joe Malin10d96952013-05-29 17:49:09 -07004908 * @param selectable Whether the content of this TextView should be selectable.
Gilles Debunne86b9c782010-11-11 10:43:48 -08004909 */
4910 public void setTextIsSelectable(boolean selectable) {
Gilles Debunne60e21862012-01-30 15:04:14 -08004911 if (!selectable && mEditor == null) return; // false is default value with no edit data
Gilles Debunne86b9c782010-11-11 10:43:48 -08004912
Gilles Debunne5fae9962012-05-08 14:53:20 -07004913 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07004914 if (mEditor.mTextIsSelectable == selectable) return;
Gilles Debunne86b9c782010-11-11 10:43:48 -08004915
Gilles Debunne2d373a12012-04-20 15:32:19 -07004916 mEditor.mTextIsSelectable = selectable;
Gilles Debunnecbcb3452010-12-17 15:31:02 -08004917 setFocusableInTouchMode(selectable);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004918 setFocusable(selectable);
4919 setClickable(selectable);
4920 setLongClickable(selectable);
4921
Gilles Debunne60e21862012-01-30 15:04:14 -08004922 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
Gilles Debunne86b9c782010-11-11 10:43:48 -08004923
4924 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
Gilles Debunne857c3412012-06-07 10:50:58 -07004925 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
Gilles Debunne86b9c782010-11-11 10:43:48 -08004926
4927 // Called by setText above, but safer in case of future code changes
Gilles Debunne2d373a12012-04-20 15:32:19 -07004928 mEditor.prepareCursorControllers();
Gilles Debunne86b9c782010-11-11 10:43:48 -08004929 }
4930
4931 @Override
4932 protected int[] onCreateDrawableState(int extraSpace) {
Gilles Debunnefb817032011-01-13 13:52:49 -08004933 final int[] drawableState;
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004934
Gilles Debunnefb817032011-01-13 13:52:49 -08004935 if (mSingleLine) {
4936 drawableState = super.onCreateDrawableState(extraSpace);
4937 } else {
4938 drawableState = super.onCreateDrawableState(extraSpace + 1);
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004939 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
4940 }
4941
Gilles Debunne60e21862012-01-30 15:04:14 -08004942 if (isTextSelectable()) {
Gilles Debunne86b9c782010-11-11 10:43:48 -08004943 // Disable pressed state, which was introduced when TextView was made clickable.
4944 // Prevents text color change.
4945 // setClickable(false) would have a similar effect, but it also disables focus changes
4946 // and long press actions, which are both needed by text selection.
4947 final int length = drawableState.length;
4948 for (int i = 0; i < length; i++) {
4949 if (drawableState[i] == R.attr.state_pressed) {
4950 final int[] nonPressedState = new int[length - 1];
4951 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
4952 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
4953 return nonPressedState;
4954 }
4955 }
4956 }
Gilles Debunneda0a3f02010-12-22 10:07:15 -08004957
Gilles Debunne86b9c782010-11-11 10:43:48 -08004958 return drawableState;
4959 }
4960
Gilles Debunne83051b82012-02-24 20:01:13 -08004961 private Path getUpdatedHighlightPath() {
4962 Path highlight = null;
4963 Paint highlightPaint = mHighlightPaint;
4964
4965 final int selStart = getSelectionStart();
4966 final int selEnd = getSelectionEnd();
4967 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
4968 if (selStart == selEnd) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07004969 if (mEditor != null && mEditor.isCursorVisible() &&
4970 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
Gilles Debunned88876a2012-03-16 17:34:04 -07004971 (2 * Editor.BLINK) < Editor.BLINK) {
Gilles Debunne83051b82012-02-24 20:01:13 -08004972 if (mHighlightPathBogus) {
4973 if (mHighlightPath == null) mHighlightPath = new Path();
4974 mHighlightPath.reset();
4975 mLayout.getCursorPath(selStart, mHighlightPath, mText);
Gilles Debunne2d373a12012-04-20 15:32:19 -07004976 mEditor.updateCursorsPositions();
Gilles Debunne83051b82012-02-24 20:01:13 -08004977 mHighlightPathBogus = false;
4978 }
4979
4980 // XXX should pass to skin instead of drawing directly
4981 highlightPaint.setColor(mCurTextColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004982 highlightPaint.setStyle(Paint.Style.STROKE);
4983 highlight = mHighlightPath;
4984 }
4985 } else {
4986 if (mHighlightPathBogus) {
4987 if (mHighlightPath == null) mHighlightPath = new Path();
4988 mHighlightPath.reset();
4989 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
4990 mHighlightPathBogus = false;
4991 }
4992
4993 // XXX should pass to skin instead of drawing directly
4994 highlightPaint.setColor(mHighlightColor);
Gilles Debunne83051b82012-02-24 20:01:13 -08004995 highlightPaint.setStyle(Paint.Style.FILL);
4996
4997 highlight = mHighlightPath;
4998 }
4999 }
5000 return highlight;
5001 }
5002
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005003 /**
5004 * @hide
5005 */
5006 public int getHorizontalOffsetForDrawables() {
5007 return 0;
5008 }
5009
Romain Guyc4d8eb62010-08-18 20:48:33 -07005010 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005011 protected void onDraw(Canvas canvas) {
Romain Guy986003d2009-03-25 17:42:35 -07005012 restartMarqueeIfNeeded();
5013
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005014 // Draw the background for this view
5015 super.onDraw(canvas);
5016
5017 final int compoundPaddingLeft = getCompoundPaddingLeft();
5018 final int compoundPaddingTop = getCompoundPaddingTop();
5019 final int compoundPaddingRight = getCompoundPaddingRight();
5020 final int compoundPaddingBottom = getCompoundPaddingBottom();
5021 final int scrollX = mScrollX;
5022 final int scrollY = mScrollY;
5023 final int right = mRight;
5024 final int left = mLeft;
5025 final int bottom = mBottom;
5026 final int top = mTop;
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005027 final boolean isLayoutRtl = isLayoutRtl();
5028 final int offset = getHorizontalOffsetForDrawables();
5029 final int leftOffset = isLayoutRtl ? 0 : offset;
5030 final int rightOffset = isLayoutRtl ? offset : 0 ;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005031
5032 final Drawables dr = mDrawables;
5033 if (dr != null) {
5034 /*
5035 * Compound, not extended, because the icon is not clipped
5036 * if the text height is smaller.
5037 */
5038
5039 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5040 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5041
Romain Guy3c77d392009-05-20 11:26:50 -07005042 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5043 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005044 if (dr.mDrawableLeft != null) {
5045 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005046 canvas.translate(scrollX + mPaddingLeft + leftOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005047 scrollY + compoundPaddingTop +
5048 (vspace - dr.mDrawableHeightLeft) / 2);
5049 dr.mDrawableLeft.draw(canvas);
5050 canvas.restore();
5051 }
5052
Romain Guy3c77d392009-05-20 11:26:50 -07005053 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5054 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005055 if (dr.mDrawableRight != null) {
5056 canvas.save();
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -08005057 canvas.translate(scrollX + right - left - mPaddingRight
5058 - dr.mDrawableSizeRight - rightOffset,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005059 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5060 dr.mDrawableRight.draw(canvas);
5061 canvas.restore();
5062 }
5063
Romain Guy3c77d392009-05-20 11:26:50 -07005064 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5065 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005066 if (dr.mDrawableTop != null) {
5067 canvas.save();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005068 canvas.translate(scrollX + compoundPaddingLeft +
5069 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005070 dr.mDrawableTop.draw(canvas);
5071 canvas.restore();
5072 }
5073
Romain Guy3c77d392009-05-20 11:26:50 -07005074 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5075 // Make sure to update invalidateDrawable() when changing this code.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005076 if (dr.mDrawableBottom != null) {
5077 canvas.save();
5078 canvas.translate(scrollX + compoundPaddingLeft +
5079 (hspace - dr.mDrawableWidthBottom) / 2,
5080 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5081 dr.mDrawableBottom.draw(canvas);
5082 canvas.restore();
5083 }
5084 }
5085
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005086 int color = mCurTextColor;
5087
5088 if (mLayout == null) {
5089 assumeLayout();
5090 }
5091
5092 Layout layout = mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005093
5094 if (mHint != null && mText.length() == 0) {
5095 if (mHintTextColor != null) {
5096 color = mCurHintTextColor;
5097 }
5098
5099 layout = mHintLayout;
5100 }
5101
5102 mTextPaint.setColor(color);
5103 mTextPaint.drawableState = getDrawableState();
5104
5105 canvas.save();
5106 /* Would be faster if we didn't have to do this. Can we chop the
5107 (displayable) text so that we don't need to do this ever?
5108 */
5109
5110 int extendedPaddingTop = getExtendedPaddingTop();
5111 int extendedPaddingBottom = getExtendedPaddingBottom();
5112
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005113 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5114 final int maxScrollY = mLayout.getHeight() - vspace;
5115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005116 float clipLeft = compoundPaddingLeft + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005117 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005118 float clipRight = right - left - compoundPaddingRight + scrollX;
Fabrice Di Meglio132bda12012-02-07 17:02:00 -08005119 float clipBottom = bottom - top + scrollY -
5120 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005121
5122 if (mShadowRadius != 0) {
5123 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5124 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5125
5126 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5127 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5128 }
5129
5130 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5131
5132 int voffsetText = 0;
5133 int voffsetCursor = 0;
5134
5135 // translate in by our padding
Gilles Debunne60e21862012-01-30 15:04:14 -08005136 /* shortcircuit calling getVerticaOffset() */
5137 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5138 voffsetText = getVerticalOffset(false);
5139 voffsetCursor = getVerticalOffset(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005140 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005141 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005142
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07005143 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07005144 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Adam Powell282e3772011-08-30 16:51:11 -07005145 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5146 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005147 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07005148 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005149 final int width = mRight - mLeft;
5150 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5151 final float dx = mLayout.getLineRight(0) - (width - padding);
5152 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005153 }
5154
5155 if (mMarquee != null && mMarquee.isRunning()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005156 final float dx = -mMarquee.getScroll();
5157 canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005158 }
5159 }
5160
Gilles Debunne12d91ce2010-12-10 11:36:29 -08005161 final int cursorOffsetVertical = voffsetCursor - voffsetText;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005162
Gilles Debunne83051b82012-02-24 20:01:13 -08005163 Path highlight = getUpdatedHighlightPath();
Gilles Debunne60e21862012-01-30 15:04:14 -08005164 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005165 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08005166 } else {
Gilles Debunne83051b82012-02-24 20:01:13 -08005167 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Gilles Debunned88876a2012-03-16 17:34:04 -07005168 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005169
Gilles Debunned88876a2012-03-16 17:34:04 -07005170 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07005171 final int dx = (int) mMarquee.getGhostOffset();
5172 canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
Gilles Debunned88876a2012-03-16 17:34:04 -07005173 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
Romain Guyc2303192009-04-03 17:37:18 -07005174 }
5175
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005176 canvas.restore();
Leon Scroggins56426252010-11-01 15:45:37 -04005177 }
5178
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005179 @Override
5180 public void getFocusedRect(Rect r) {
5181 if (mLayout == null) {
5182 super.getFocusedRect(r);
5183 return;
5184 }
5185
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005186 int selEnd = getSelectionEnd();
5187 if (selEnd < 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005188 super.getFocusedRect(r);
5189 return;
5190 }
5191
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005192 int selStart = getSelectionStart();
5193 if (selStart < 0 || selStart >= selEnd) {
5194 int line = mLayout.getLineForOffset(selEnd);
5195 r.top = mLayout.getLineTop(line);
5196 r.bottom = mLayout.getLineBottom(line);
5197 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5198 r.right = r.left + 4;
5199 } else {
5200 int lineStart = mLayout.getLineForOffset(selStart);
5201 int lineEnd = mLayout.getLineForOffset(selEnd);
5202 r.top = mLayout.getLineTop(lineStart);
5203 r.bottom = mLayout.getLineBottom(lineEnd);
5204 if (lineStart == lineEnd) {
5205 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5206 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5207 } else {
Gilles Debunne60e21862012-01-30 15:04:14 -08005208 // Selection extends across multiple lines -- make the focused
5209 // rect cover the entire width.
Gilles Debunne83051b82012-02-24 20:01:13 -08005210 if (mHighlightPathBogus) {
5211 if (mHighlightPath == null) mHighlightPath = new Path();
5212 mHighlightPath.reset();
5213 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5214 mHighlightPathBogus = false;
5215 }
5216 synchronized (TEMP_RECTF) {
5217 mHighlightPath.computeBounds(TEMP_RECTF, true);
5218 r.left = (int)TEMP_RECTF.left-1;
5219 r.right = (int)TEMP_RECTF.right+1;
Dianne Hackborn70a3f672011-08-08 14:32:41 -07005220 }
5221 }
5222 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005223
5224 // Adjust for padding and gravity.
5225 int paddingLeft = getCompoundPaddingLeft();
5226 int paddingTop = getExtendedPaddingTop();
5227 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5228 paddingTop += getVerticalOffset(false);
5229 }
5230 r.offset(paddingLeft, paddingTop);
Gilles Debunne322044a2012-02-22 12:01:40 -08005231 int paddingBottom = getExtendedPaddingBottom();
5232 r.bottom += paddingBottom;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005233 }
5234
5235 /**
5236 * Return the number of lines of text, or 0 if the internal Layout has not
5237 * been built.
5238 */
5239 public int getLineCount() {
5240 return mLayout != null ? mLayout.getLineCount() : 0;
5241 }
5242
5243 /**
5244 * Return the baseline for the specified line (0...getLineCount() - 1)
5245 * If bounds is not null, return the top, left, right, bottom extents
5246 * of the specified line in it. If the internal Layout has not been built,
5247 * return 0 and set bounds to (0, 0, 0, 0)
5248 * @param line which line to examine (0..getLineCount() - 1)
5249 * @param bounds Optional. If not null, it returns the extent of the line
5250 * @return the Y-coordinate of the baseline
5251 */
5252 public int getLineBounds(int line, Rect bounds) {
5253 if (mLayout == null) {
5254 if (bounds != null) {
5255 bounds.set(0, 0, 0, 0);
5256 }
5257 return 0;
5258 }
5259 else {
5260 int baseline = mLayout.getLineBounds(line, bounds);
5261
5262 int voffset = getExtendedPaddingTop();
5263 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5264 voffset += getVerticalOffset(true);
5265 }
5266 if (bounds != null) {
5267 bounds.offset(getCompoundPaddingLeft(), voffset);
5268 }
5269 return baseline + voffset;
5270 }
5271 }
5272
5273 @Override
5274 public int getBaseline() {
5275 if (mLayout == null) {
5276 return super.getBaseline();
5277 }
5278
5279 int voffset = 0;
5280 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5281 voffset = getVerticalOffset(true);
5282 }
5283
Philip Milne7b757812012-09-19 18:13:44 -07005284 if (isLayoutModeOptical(mParent)) {
5285 voffset -= getOpticalInsets().top;
5286 }
5287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005288 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5289 }
5290
Romain Guyf2fc4602011-07-19 15:20:03 -07005291 /**
5292 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005293 */
5294 @Override
5295 protected int getFadeTop(boolean offsetRequired) {
Romain Guy59f13c7d2011-07-19 18:35:33 -07005296 if (mLayout == null) return 0;
5297
Romain Guyf2fc4602011-07-19 15:20:03 -07005298 int voffset = 0;
5299 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5300 voffset = getVerticalOffset(true);
5301 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005302
Romain Guyf2fc4602011-07-19 15:20:03 -07005303 if (offsetRequired) voffset += getTopPaddingOffset();
5304
5305 return getExtendedPaddingTop() + voffset;
5306 }
5307
5308 /**
5309 * @hide
Romain Guyf2fc4602011-07-19 15:20:03 -07005310 */
Gilles Debunne3784a7f2011-07-15 13:49:38 -07005311 @Override
Romain Guyf2fc4602011-07-19 15:20:03 -07005312 protected int getFadeHeight(boolean offsetRequired) {
5313 return mLayout != null ? mLayout.getHeight() : 0;
5314 }
5315
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005316 @Override
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005317 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5318 if (keyCode == KeyEvent.KEYCODE_BACK) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005319 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005320
Gilles Debunne28294cc2011-08-24 12:02:05 -07005321 if (isInSelectionMode) {
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005322 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5323 KeyEvent.DispatcherState state = getKeyDispatcherState();
5324 if (state != null) {
5325 state.startTracking(event, this);
5326 }
5327 return true;
5328 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5329 KeyEvent.DispatcherState state = getKeyDispatcherState();
5330 if (state != null) {
5331 state.handleUpEvent(event);
5332 }
5333 if (event.isTracking() && !event.isCanceled()) {
Gilles Debunne14568c32012-01-13 15:26:05 -08005334 stopSelectionActionMode();
5335 return true;
Gilles Debunne4a7199a2011-07-11 14:58:27 -07005336 }
5337 }
5338 }
5339 }
5340 return super.onKeyPreIme(keyCode, event);
5341 }
5342
5343 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005344 public boolean onKeyDown(int keyCode, KeyEvent event) {
5345 int which = doKeyDown(keyCode, event, null);
5346 if (which == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005347 return super.onKeyDown(keyCode, event);
5348 }
5349
5350 return true;
5351 }
5352
5353 @Override
5354 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005355 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005356
5357 int which = doKeyDown(keyCode, down, event);
5358 if (which == 0) {
5359 // Go through default dispatching.
5360 return super.onKeyMultiple(keyCode, repeatCount, event);
5361 }
5362 if (which == -1) {
5363 // Consumed the whole thing.
5364 return true;
5365 }
5366
5367 repeatCount--;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005369 // We are going to dispatch the remaining events to either the input
5370 // or movement method. To do this, we will just send a repeated stream
5371 // of down and up events until we have done the complete repeatCount.
5372 // It would be nice if those interfaces had an onKeyMultiple() method,
5373 // but adding that is a more complicated change.
The Android Open Source Project10592532009-03-18 17:39:46 -07005374 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005375 if (which == 1) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005376 // mEditor and mEditor.mInput are not null from doKeyDown
5377 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005378 while (--repeatCount > 0) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005379 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5380 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005381 }
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005382 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005383
5384 } else if (which == 2) {
Gilles Debunne60e21862012-01-30 15:04:14 -08005385 // mMovement is not null from doKeyDown
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005386 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5387 while (--repeatCount > 0) {
5388 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5389 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5390 }
5391 }
5392
5393 return true;
5394 }
5395
5396 /**
5397 * Returns true if pressing ENTER in this field advances focus instead
5398 * of inserting the character. This is true mostly in single-line fields,
5399 * but also in mail addresses and subjects which will display on multiple
5400 * lines but where it doesn't make sense to insert newlines.
5401 */
The Android Open Source Project4df24232009-03-05 14:34:35 -08005402 private boolean shouldAdvanceFocusOnEnter() {
Gilles Debunne60e21862012-01-30 15:04:14 -08005403 if (getKeyListener() == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005404 return false;
5405 }
5406
5407 if (mSingleLine) {
5408 return true;
5409 }
5410
Gilles Debunne2d373a12012-04-20 15:32:19 -07005411 if (mEditor != null &&
5412 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5413 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005414 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5415 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005416 return true;
5417 }
5418 }
5419
5420 return false;
5421 }
5422
Jeff Brown4e6319b2010-12-13 10:36:51 -08005423 /**
5424 * Returns true if pressing TAB in this field advances focus instead
5425 * of inserting the character. Insert tabs only in multi-line editors.
5426 */
5427 private boolean shouldAdvanceFocusOnTab() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005428 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5429 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5430 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5431 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5432 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5433 return false;
Jeff Brown4e6319b2010-12-13 10:36:51 -08005434 }
5435 }
5436 return true;
5437 }
5438
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005439 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5440 if (!isEnabled()) {
5441 return 0;
5442 }
5443
Michael Wright3a7e4832013-02-11 15:55:50 -08005444 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5445 // While this shouldn't be necessary because any time we're preventing default movement we
5446 // should be restricting the focus to remain within this view, thus we'll also receive
5447 // the key up event, occasionally key up events will get dropped and we don't want to
5448 // prevent the user from traversing out of this on the next key down.
5449 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5450 mPreventDefaultMovement = false;
5451 }
5452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005453 switch (keyCode) {
5454 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005455 if (event.hasNoModifiers()) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005456 // When mInputContentType is set, we know that we are
5457 // running in a "modern" cupcake environment, so don't need
5458 // to worry about the application trying to capture
5459 // enter key events.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005460 if (mEditor != null && mEditor.mInputContentType != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005461 // If there is an action listener, given them a
5462 // chance to consume the event.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005463 if (mEditor.mInputContentType.onEditorActionListener != null &&
5464 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
The Android Open Source Project10592532009-03-18 17:39:46 -07005465 this, EditorInfo.IME_NULL, event)) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005466 mEditor.mInputContentType.enterDown = true;
The Android Open Source Project10592532009-03-18 17:39:46 -07005467 // We are consuming the enter key for them.
5468 return -1;
5469 }
5470 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005471
The Android Open Source Project10592532009-03-18 17:39:46 -07005472 // If our editor should move focus when enter is pressed, or
5473 // this is a generated event from an IME action button, then
5474 // don't let it be inserted into the text.
Jeff Brown4e6319b2010-12-13 10:36:51 -08005475 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
The Android Open Source Project10592532009-03-18 17:39:46 -07005476 || shouldAdvanceFocusOnEnter()) {
Dianne Hackborn0500b3c2011-11-01 15:28:43 -07005477 if (hasOnClickListeners()) {
Leon Scroggins7014b122011-01-11 15:17:34 -05005478 return 0;
5479 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005480 return -1;
5481 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005482 }
The Android Open Source Project10592532009-03-18 17:39:46 -07005483 break;
Gilles Debunne2d373a12012-04-20 15:32:19 -07005484
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005485 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005486 if (event.hasNoModifiers()) {
5487 if (shouldAdvanceFocusOnEnter()) {
5488 return 0;
5489 }
5490 }
5491 break;
5492
5493 case KeyEvent.KEYCODE_TAB:
5494 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5495 if (shouldAdvanceFocusOnTab()) {
5496 return 0;
5497 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005498 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005499 break;
5500
5501 // Has to be done on key down (and not on key up) to correctly be intercepted.
5502 case KeyEvent.KEYCODE_BACK:
Gilles Debunne2d373a12012-04-20 15:32:19 -07005503 if (mEditor != null && mEditor.mSelectionActionMode != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07005504 stopSelectionActionMode();
5505 return -1;
5506 }
5507 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005508 }
5509
Gilles Debunne2d373a12012-04-20 15:32:19 -07005510 if (mEditor != null && mEditor.mKeyListener != null) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005511 resetErrorChangedFlag();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005512
5513 boolean doDown = true;
5514 if (otherEvent != null) {
5515 try {
5516 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005517 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5518 otherEvent);
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005519 hideErrorIfUnchanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005520 doDown = false;
5521 if (handled) {
5522 return -1;
5523 }
5524 } catch (AbstractMethodError e) {
5525 // onKeyOther was added after 1.0, so if it isn't
5526 // implemented we need to try to dispatch as a regular down.
5527 } finally {
5528 endBatchEdit();
5529 }
5530 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005531
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005532 if (doDown) {
5533 beginBatchEdit();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005534 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5535 keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005536 endBatchEdit();
Gilles Debunne12ab6452011-01-30 12:08:25 -08005537 hideErrorIfUnchanged();
5538 if (handled) return 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005539 }
5540 }
5541
5542 // bug 650865: sometimes we get a key event before a layout.
5543 // don't try to move around if we don't know the layout.
5544
5545 if (mMovement != null && mLayout != null) {
5546 boolean doDown = true;
5547 if (otherEvent != null) {
5548 try {
5549 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5550 otherEvent);
5551 doDown = false;
5552 if (handled) {
5553 return -1;
5554 }
5555 } catch (AbstractMethodError e) {
5556 // onKeyOther was added after 1.0, so if it isn't
5557 // implemented we need to try to dispatch as a regular down.
5558 }
5559 }
5560 if (doDown) {
Michael Wright3a7e4832013-02-11 15:55:50 -08005561 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5562 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5563 mPreventDefaultMovement = true;
5564 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005565 return 2;
Michael Wright3a7e4832013-02-11 15:55:50 -08005566 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005567 }
5568 }
5569
Michael Wright3a7e4832013-02-11 15:55:50 -08005570 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005571 }
5572
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005573 /**
5574 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5575 * can be recorded.
5576 * @hide
5577 */
5578 public void resetErrorChangedFlag() {
5579 /*
5580 * Keep track of what the error was before doing the input
5581 * so that if an input filter changed the error, we leave
5582 * that error showing. Otherwise, we take down whatever
5583 * error was showing when the user types something.
5584 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07005585 if (mEditor != null) mEditor.mErrorWasChanged = false;
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005586 }
5587
5588 /**
5589 * @hide
5590 */
5591 public void hideErrorIfUnchanged() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005592 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
Gilles Debunneb7fc63f2011-01-27 15:55:29 -08005593 setError(null, null);
5594 }
5595 }
5596
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005597 @Override
5598 public boolean onKeyUp(int keyCode, KeyEvent event) {
5599 if (!isEnabled()) {
5600 return super.onKeyUp(keyCode, event);
5601 }
5602
Michael Wright3a7e4832013-02-11 15:55:50 -08005603 if (!KeyEvent.isModifierKey(keyCode)) {
5604 mPreventDefaultMovement = false;
5605 }
5606
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005607 switch (keyCode) {
5608 case KeyEvent.KEYCODE_DPAD_CENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005609 if (event.hasNoModifiers()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005610 /*
5611 * If there is a click listener, just call through to
5612 * super, which will invoke it.
5613 *
Jeff Brown4e6319b2010-12-13 10:36:51 -08005614 * If there isn't a click listener, try to show the soft
5615 * input method. (It will also
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005616 * call performClick(), but that won't do anything in
5617 * this case.)
5618 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005619 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005620 if (mMovement != null && mText instanceof Editable
5621 && mLayout != null && onCheckIsTextEditor()) {
Gilles Debunne17d31de2011-01-27 11:02:18 -08005622 InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09005623 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07005624 if (imm != null && getShowSoftInputOnFocus()) {
satok863fcd62011-06-21 17:38:02 +09005625 imm.showSoftInput(this, 0);
5626 }
Jeff Brown4e6319b2010-12-13 10:36:51 -08005627 }
5628 }
5629 }
5630 return super.onKeyUp(keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005631
Jeff Brown4e6319b2010-12-13 10:36:51 -08005632 case KeyEvent.KEYCODE_ENTER:
Jeff Brown4e6319b2010-12-13 10:36:51 -08005633 if (event.hasNoModifiers()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005634 if (mEditor != null && mEditor.mInputContentType != null
5635 && mEditor.mInputContentType.onEditorActionListener != null
5636 && mEditor.mInputContentType.enterDown) {
5637 mEditor.mInputContentType.enterDown = false;
5638 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
Jeff Brown4e6319b2010-12-13 10:36:51 -08005639 this, EditorInfo.IME_NULL, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005640 return true;
5641 }
5642 }
5643
Jeff Brown4e6319b2010-12-13 10:36:51 -08005644 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5645 || shouldAdvanceFocusOnEnter()) {
5646 /*
5647 * If there is a click listener, just call through to
5648 * super, which will invoke it.
5649 *
5650 * If there isn't a click listener, try to advance focus,
5651 * but still call through to super, which will reset the
5652 * pressed state and longpress state. (It will also
5653 * call performClick(), but that won't do anything in
5654 * this case.)
5655 */
Gilles Debunne06a8e9b2011-12-08 10:39:39 -08005656 if (!hasOnClickListeners()) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005657 View v = focusSearch(FOCUS_DOWN);
5658
5659 if (v != null) {
5660 if (!v.requestFocus(FOCUS_DOWN)) {
5661 throw new IllegalStateException(
5662 "focus search returned a view " +
5663 "that wasn't able to take focus!");
5664 }
5665
5666 /*
5667 * Return true because we handled the key; super
5668 * will return false because there was no click
5669 * listener.
5670 */
5671 super.onKeyUp(keyCode, event);
5672 return true;
5673 } else if ((event.getFlags()
5674 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5675 // No target for next focus, but make sure the IME
5676 // if this came from it.
5677 InputMethodManager imm = InputMethodManager.peekInstance();
Gilles Debunne17d31de2011-01-27 11:02:18 -08005678 if (imm != null && imm.isActive(this)) {
Jeff Brown4e6319b2010-12-13 10:36:51 -08005679 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5680 }
5681 }
5682 }
5683 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005684 return super.onKeyUp(keyCode, event);
5685 }
Gilles Debunne64e54a62010-09-07 19:07:17 -07005686 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005687 }
5688
Gilles Debunne2d373a12012-04-20 15:32:19 -07005689 if (mEditor != null && mEditor.mKeyListener != null)
5690 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005691 return true;
5692
5693 if (mMovement != null && mLayout != null)
5694 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5695 return true;
5696
5697 return super.onKeyUp(keyCode, event);
5698 }
5699
Gilles Debunnec1714022012-01-17 13:59:23 -08005700 @Override
5701 public boolean onCheckIsTextEditor() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005702 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005703 }
Gilles Debunneb062e812011-09-27 14:58:37 -07005704
Gilles Debunnec1714022012-01-17 13:59:23 -08005705 @Override
5706 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
Janos Levai042856c2010-10-15 02:53:58 +03005707 if (onCheckIsTextEditor() && isEnabled()) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005708 mEditor.createInputMethodStateIfNeeded();
Gilles Debunne60e21862012-01-30 15:04:14 -08005709 outAttrs.inputType = getInputType();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005710 if (mEditor.mInputContentType != null) {
5711 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5712 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5713 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5714 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5715 outAttrs.extras = mEditor.mInputContentType.extras;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005716 } else {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005717 outAttrs.imeOptions = EditorInfo.IME_NULL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005718 }
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005719 if (focusSearch(FOCUS_DOWN) != null) {
5720 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5721 }
5722 if (focusSearch(FOCUS_UP) != null) {
5723 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5724 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005725 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5726 == EditorInfo.IME_ACTION_UNSPECIFIED) {
Dianne Hackborndea3ef72010-10-28 14:24:22 -07005727 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005728 // An action has not been set, but the enter key will move to
5729 // the next focus, so set the action to that.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005730 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005731 } else {
5732 // An action has not been set, and there is no focus to move
5733 // to, so let's just supply a "done" action.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005734 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
The Android Open Source Project4df24232009-03-05 14:34:35 -08005735 }
5736 if (!shouldAdvanceFocusOnEnter()) {
5737 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005738 }
5739 }
Gilles Debunne91a08cf2010-11-08 17:34:49 -08005740 if (isMultilineInputType(outAttrs.inputType)) {
The Android Open Source Project10592532009-03-18 17:39:46 -07005741 // Multi-line text editors should always show an enter key.
5742 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
5743 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005744 outAttrs.hintText = mHint;
5745 if (mText instanceof Editable) {
5746 InputConnection ic = new EditableInputConnection(this);
Gilles Debunne05336272010-07-09 20:13:45 -07005747 outAttrs.initialSelStart = getSelectionStart();
5748 outAttrs.initialSelEnd = getSelectionEnd();
Gilles Debunne60e21862012-01-30 15:04:14 -08005749 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005750 return ic;
5751 }
5752 }
5753 return null;
5754 }
5755
5756 /**
5757 * If this TextView contains editable content, extract a portion of it
5758 * based on the information in <var>request</var> in to <var>outText</var>.
5759 * @return Returns true if the text was successfully extracted, else false.
5760 */
Gilles Debunned88876a2012-03-16 17:34:04 -07005761 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07005762 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07005763 return mEditor.extractText(request, outText);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005764 }
Viktor Yakovel964be412010-02-17 08:35:57 +01005765
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005766 /**
5767 * This is used to remove all style-impacting spans from text before new
5768 * extracted text is being replaced into it, so that we don't have any
5769 * lingering spans applied during the replace.
5770 */
5771 static void removeParcelableSpans(Spannable spannable, int start, int end) {
5772 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
5773 int i = spans.length;
5774 while (i > 0) {
5775 i--;
5776 spannable.removeSpan(spans[i]);
5777 }
5778 }
Gilles Debunned88876a2012-03-16 17:34:04 -07005779
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005780 /**
5781 * Apply to this text view the given extracted text, as previously
5782 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
5783 */
5784 public void setExtractedText(ExtractedText text) {
5785 Editable content = getEditableText();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005786 if (text.text != null) {
5787 if (content == null) {
5788 setText(text.text, TextView.BufferType.EDITABLE);
5789 } else if (text.partialStartOffset < 0) {
5790 removeParcelableSpans(content, 0, content.length());
5791 content.replace(0, content.length(), text.text);
5792 } else {
5793 final int N = content.length();
5794 int start = text.partialStartOffset;
5795 if (start > N) start = N;
5796 int end = text.partialEndOffset;
5797 if (end > N) end = N;
5798 removeParcelableSpans(content, start, end);
5799 content.replace(start, end, text.text);
5800 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005801 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005802
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005803 // Now set the selection position... make sure it is in range, to
5804 // avoid crashes. If this is a partial update, it is possible that
5805 // the underlying text may have changed, causing us problems here.
5806 // Also we just don't want to trust clients to do the right thing.
5807 Spannable sp = (Spannable)getText();
5808 final int N = sp.length();
5809 int start = text.selectionStart;
5810 if (start < 0) start = 0;
5811 else if (start > N) start = N;
5812 int end = text.selectionEnd;
5813 if (end < 0) end = 0;
5814 else if (end > N) end = N;
5815 Selection.setSelection(sp, start, end);
Gilles Debunne2d373a12012-04-20 15:32:19 -07005816
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07005817 // Finally, update the selection mode.
5818 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
5819 MetaKeyKeyListener.startSelecting(this, sp);
5820 } else {
5821 MetaKeyKeyListener.stopSelecting(this, sp);
5822 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005823 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005824
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005825 /**
5826 * @hide
5827 */
5828 public void setExtracting(ExtractedTextRequest req) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005829 if (mEditor.mInputMethodState != null) {
5830 mEditor.mInputMethodState.mExtractedTextRequest = req;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005831 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005832 // This would stop a possible selection mode, but no such mode is started in case
5833 // extracted mode will start. Some text is selected though, and will trigger an action mode
5834 // in the extracted view.
Gilles Debunne2d373a12012-04-20 15:32:19 -07005835 mEditor.hideControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005836 }
Gilles Debunne98fb9ed2011-09-07 17:15:41 -07005837
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005838 /**
5839 * Called by the framework in response to a text completion from
5840 * the current input method, provided by it calling
5841 * {@link InputConnection#commitCompletion
5842 * InputConnection.commitCompletion()}. The default implementation does
5843 * nothing; text views that are supporting auto-completion should override
5844 * this to do their desired behavior.
5845 *
5846 * @param text The auto complete text the user has selected.
5847 */
5848 public void onCommitCompletion(CompletionInfo text) {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005849 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005850 }
5851
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005852 /**
5853 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
5854 * a dictionnary) from the current input method, provided by it calling
5855 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
5856 * implementation flashes the background of the corrected word to provide feedback to the user.
5857 *
5858 * @param info The auto correct info about the text that was corrected.
5859 */
5860 public void onCommitCorrection(CorrectionInfo info) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005861 if (mEditor != null) mEditor.onCommitCorrection(info);
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08005862 }
5863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005864 public void beginBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005865 if (mEditor != null) mEditor.beginBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005866 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005867
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005868 public void endBatchEdit() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07005869 if (mEditor != null) mEditor.endBatchEdit();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005870 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005871
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005872 /**
5873 * Called by the framework in response to a request to begin a batch
5874 * of edit operations through a call to link {@link #beginBatchEdit()}.
5875 */
5876 public void onBeginBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005877 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005878 }
Gilles Debunne60e21862012-01-30 15:04:14 -08005879
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005880 /**
5881 * Called by the framework in response to a request to end a batch
5882 * of edit operations through a call to link {@link #endBatchEdit}.
5883 */
5884 public void onEndBatchEdit() {
Gilles Debunned40cc3c2011-03-11 16:59:32 -08005885 // intentionally empty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005886 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07005887
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005888 /**
5889 * Called by the framework in response to a private command from the
5890 * current method, provided by it calling
5891 * {@link InputConnection#performPrivateCommand
5892 * InputConnection.performPrivateCommand()}.
5893 *
5894 * @param action The action name of the command.
5895 * @param data Any additional data for the command. This may be null.
5896 * @return Return true if you handled the command, else false.
5897 */
5898 public boolean onPrivateIMECommand(String action, Bundle data) {
5899 return false;
5900 }
5901
5902 private void nullLayouts() {
5903 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
5904 mSavedLayout = (BoringLayout) mLayout;
5905 }
5906 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
5907 mSavedHintLayout = (BoringLayout) mHintLayout;
5908 }
5909
Adam Powell282e3772011-08-30 16:51:11 -07005910 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
Gilles Debunne77f18b02010-10-22 14:28:25 -07005911
Fabrice Di Megliod4c3b8e2011-11-09 18:04:07 -08005912 mBoring = mHintBoring = null;
5913
Gilles Debunne77f18b02010-10-22 14:28:25 -07005914 // Since it depends on the value of mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07005915 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005916 }
5917
5918 /**
5919 * Make a new Layout based on the already-measured size of the view,
5920 * on the assumption that it was measured correctly at some point.
5921 */
5922 private void assumeLayout() {
5923 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
5924
5925 if (width < 1) {
5926 width = 0;
5927 }
5928
5929 int physicalWidth = width;
5930
5931 if (mHorizontallyScrolling) {
Jeff Brown033a0012011-11-11 15:30:16 -08005932 width = VERY_WIDE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005933 }
5934
5935 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
5936 physicalWidth, false);
5937 }
5938
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005939 private Layout.Alignment getLayoutAlignment() {
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005940 Layout.Alignment alignment;
5941 switch (getTextAlignment()) {
5942 case TEXT_ALIGNMENT_GRAVITY:
5943 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
5944 case Gravity.START:
5945 alignment = Layout.Alignment.ALIGN_NORMAL;
5946 break;
5947 case Gravity.END:
5948 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5949 break;
5950 case Gravity.LEFT:
5951 alignment = Layout.Alignment.ALIGN_LEFT;
5952 break;
5953 case Gravity.RIGHT:
5954 alignment = Layout.Alignment.ALIGN_RIGHT;
5955 break;
5956 case Gravity.CENTER_HORIZONTAL:
5957 alignment = Layout.Alignment.ALIGN_CENTER;
5958 break;
5959 default:
5960 alignment = Layout.Alignment.ALIGN_NORMAL;
5961 break;
5962 }
5963 break;
5964 case TEXT_ALIGNMENT_TEXT_START:
5965 alignment = Layout.Alignment.ALIGN_NORMAL;
5966 break;
5967 case TEXT_ALIGNMENT_TEXT_END:
5968 alignment = Layout.Alignment.ALIGN_OPPOSITE;
5969 break;
5970 case TEXT_ALIGNMENT_CENTER:
5971 alignment = Layout.Alignment.ALIGN_CENTER;
5972 break;
5973 case TEXT_ALIGNMENT_VIEW_START:
5974 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5975 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
5976 break;
5977 case TEXT_ALIGNMENT_VIEW_END:
5978 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
5979 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
5980 break;
5981 case TEXT_ALIGNMENT_INHERIT:
5982 // This should never happen as we have already resolved the text alignment
5983 // but better safe than sorry so we just fall through
5984 default:
5985 alignment = Layout.Alignment.ALIGN_NORMAL;
5986 break;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005987 }
Fabrice Di Megliof80ceed2013-02-20 12:49:08 -08005988 return alignment;
Doug Feltc0ccf0c2011-06-23 16:13:18 -07005989 }
5990
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005991 /**
5992 * The width passed in is now the desired layout width,
5993 * not the full view width with padding.
5994 * {@hide}
5995 */
Gilles Debunne287d6c62011-10-05 18:22:11 -07005996 protected void makeNewLayout(int wantWidth, int hintWidth,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08005997 BoringLayout.Metrics boring,
5998 BoringLayout.Metrics hintBoring,
5999 int ellipsisWidth, boolean bringIntoView) {
6000 stopMarquee();
6001
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006002 // Update "old" cached values
6003 mOldMaximum = mMaximum;
6004 mOldMaxMode = mMaxMode;
6005
Gilles Debunne83051b82012-02-24 20:01:13 -08006006 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006007
Gilles Debunne287d6c62011-10-05 18:22:11 -07006008 if (wantWidth < 0) {
6009 wantWidth = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006010 }
6011 if (hintWidth < 0) {
6012 hintWidth = 0;
6013 }
6014
Doug Feltc0ccf0c2011-06-23 16:13:18 -07006015 Layout.Alignment alignment = getLayoutAlignment();
Raph Levienf5cf6c92013-04-12 11:31:31 -07006016 final boolean testDirChange = mSingleLine && mLayout != null &&
6017 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6018 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6019 int oldDir = 0;
6020 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
Gilles Debunne60e21862012-01-30 15:04:14 -08006021 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
Adam Powell282e3772011-08-30 16:51:11 -07006022 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6023 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6024 TruncateAt effectiveEllipsize = mEllipsize;
6025 if (mEllipsize == TruncateAt.MARQUEE &&
6026 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07006027 effectiveEllipsize = TruncateAt.END_SMALL;
Adam Powell282e3772011-08-30 16:51:11 -07006028 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006029
Doug Feltcb3791202011-07-07 11:57:48 -07006030 if (mTextDir == null) {
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07006031 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006032 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006033
Gilles Debunne287d6c62011-10-05 18:22:11 -07006034 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
Adam Powell282e3772011-08-30 16:51:11 -07006035 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6036 if (switchEllipsize) {
6037 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6038 TruncateAt.END : TruncateAt.MARQUEE;
Gilles Debunne287d6c62011-10-05 18:22:11 -07006039 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
Adam Powell282e3772011-08-30 16:51:11 -07006040 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006041 }
6042
Romain Guy4dc4f732009-06-19 15:16:40 -07006043 shouldEllipsize = mEllipsize != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006044 mHintLayout = null;
6045
6046 if (mHint != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006047 if (shouldEllipsize) hintWidth = wantWidth;
Romain Guy4dc4f732009-06-19 15:16:40 -07006048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006049 if (hintBoring == UNKNOWN_BORING) {
Doug Feltcb3791202011-07-07 11:57:48 -07006050 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006051 mHintBoring);
6052 if (hintBoring != null) {
6053 mHintBoring = hintBoring;
6054 }
6055 }
6056
6057 if (hintBoring != null) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006058 if (hintBoring.width <= hintWidth &&
6059 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006060 if (mSavedHintLayout != null) {
6061 mHintLayout = mSavedHintLayout.
6062 replaceOrMake(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006063 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6064 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006065 } else {
6066 mHintLayout = BoringLayout.make(mHint, mTextPaint,
Romain Guy4dc4f732009-06-19 15:16:40 -07006067 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6068 hintBoring, mIncludePad);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006069 }
6070
6071 mSavedHintLayout = (BoringLayout) mHintLayout;
Romain Guy4dc4f732009-06-19 15:16:40 -07006072 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6073 if (mSavedHintLayout != null) {
6074 mHintLayout = mSavedHintLayout.
6075 replaceOrMake(mHint, mTextPaint,
6076 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6077 hintBoring, mIncludePad, mEllipsize,
6078 ellipsisWidth);
6079 } else {
6080 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6081 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6082 hintBoring, mIncludePad, mEllipsize,
6083 ellipsisWidth);
6084 }
6085 } else if (shouldEllipsize) {
6086 mHintLayout = new StaticLayout(mHint,
6087 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006088 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006089 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006090 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006091 } else {
6092 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006093 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006094 mIncludePad);
6095 }
Romain Guy4dc4f732009-06-19 15:16:40 -07006096 } else if (shouldEllipsize) {
6097 mHintLayout = new StaticLayout(mHint,
6098 0, mHint.length(),
Doug Feltcb3791202011-07-07 11:57:48 -07006099 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
Romain Guy4dc4f732009-06-19 15:16:40 -07006100 mSpacingAdd, mIncludePad, mEllipsize,
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006101 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006102 } else {
6103 mHintLayout = new StaticLayout(mHint, mTextPaint,
Doug Feltcb3791202011-07-07 11:57:48 -07006104 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006105 mIncludePad);
6106 }
6107 }
6108
Raph Levienf5cf6c92013-04-12 11:31:31 -07006109 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006110 registerForPreDraw();
6111 }
6112
6113 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
Romain Guy939151f2009-04-08 14:22:40 -07006114 if (!compressText(ellipsisWidth)) {
6115 final int height = mLayoutParams.height;
6116 // If the size of the view does not depend on the size of the text, try to
6117 // start the marquee immediately
Romain Guy980a9382010-01-08 15:06:28 -08006118 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
Romain Guy939151f2009-04-08 14:22:40 -07006119 startMarquee();
6120 } else {
6121 // Defer the start of the marquee until we know our width (see setFrame())
6122 mRestartMarquee = true;
6123 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006124 }
6125 }
Gilles Debunnef788a9f2010-07-22 10:17:23 -07006126
6127 // CursorControllers need a non-null mLayout
Gilles Debunne2d373a12012-04-20 15:32:19 -07006128 if (mEditor != null) mEditor.prepareCursorControllers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006129 }
6130
Gilles Debunne287d6c62011-10-05 18:22:11 -07006131 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006132 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6133 boolean useSaved) {
6134 Layout result = null;
6135 if (mText instanceof Spannable) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006136 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
Adam Powell282e3772011-08-30 16:51:11 -07006137 alignment, mTextDir, mSpacingMult,
Gilles Debunne60e21862012-01-30 15:04:14 -08006138 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
Fabrice Di Meglioad0b0512011-10-04 17:21:26 -07006139 ellipsisWidth);
Adam Powell282e3772011-08-30 16:51:11 -07006140 } else {
6141 if (boring == UNKNOWN_BORING) {
6142 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6143 if (boring != null) {
6144 mBoring = boring;
6145 }
6146 }
6147
6148 if (boring != null) {
Gilles Debunne287d6c62011-10-05 18:22:11 -07006149 if (boring.width <= wantWidth &&
Adam Powell282e3772011-08-30 16:51:11 -07006150 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6151 if (useSaved && mSavedLayout != null) {
6152 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006153 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006154 boring, mIncludePad);
6155 } else {
6156 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006157 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006158 boring, mIncludePad);
6159 }
6160
6161 if (useSaved) {
6162 mSavedLayout = (BoringLayout) result;
6163 }
Gilles Debunne287d6c62011-10-05 18:22:11 -07006164 } else if (shouldEllipsize && boring.width <= wantWidth) {
Adam Powell282e3772011-08-30 16:51:11 -07006165 if (useSaved && mSavedLayout != null) {
6166 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006167 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006168 boring, mIncludePad, effectiveEllipsize,
6169 ellipsisWidth);
6170 } else {
6171 result = BoringLayout.make(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006172 wantWidth, alignment, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006173 boring, mIncludePad, effectiveEllipsize,
6174 ellipsisWidth);
6175 }
6176 } else if (shouldEllipsize) {
6177 result = new StaticLayout(mTransformed,
6178 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006179 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006180 mSpacingAdd, mIncludePad, effectiveEllipsize,
6181 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6182 } else {
6183 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006184 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006185 mIncludePad);
6186 }
6187 } else if (shouldEllipsize) {
6188 result = new StaticLayout(mTransformed,
6189 0, mTransformed.length(),
Gilles Debunne287d6c62011-10-05 18:22:11 -07006190 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
Adam Powell282e3772011-08-30 16:51:11 -07006191 mSpacingAdd, mIncludePad, effectiveEllipsize,
6192 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6193 } else {
6194 result = new StaticLayout(mTransformed, mTextPaint,
Gilles Debunne287d6c62011-10-05 18:22:11 -07006195 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
Adam Powell282e3772011-08-30 16:51:11 -07006196 mIncludePad);
6197 }
6198 }
6199 return result;
6200 }
6201
Romain Guy939151f2009-04-08 14:22:40 -07006202 private boolean compressText(float width) {
Romain Guy2bffd262010-09-12 17:40:02 -07006203 if (isHardwareAccelerated()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07006204
Romain Guy3373ed62009-05-04 14:13:32 -07006205 // Only compress the text if it hasn't been compressed by the previous pass
6206 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6207 mTextPaint.getTextScaleX() == 1.0f) {
Romain Guy939151f2009-04-08 14:22:40 -07006208 final float textWidth = mLayout.getLineWidth(0);
Romain Guy3373ed62009-05-04 14:13:32 -07006209 final float overflow = (textWidth + 1.0f - width) / width;
Romain Guy939151f2009-04-08 14:22:40 -07006210 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6211 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6212 post(new Runnable() {
6213 public void run() {
6214 requestLayout();
6215 }
6216 });
6217 return true;
6218 }
6219 }
6220
6221 return false;
6222 }
6223
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006224 private static int desired(Layout layout) {
6225 int n = layout.getLineCount();
6226 CharSequence text = layout.getText();
6227 float max = 0;
6228
6229 // if any line was wrapped, we can't use it.
6230 // but it's ok for the last line not to have a newline
6231
6232 for (int i = 0; i < n - 1; i++) {
6233 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6234 return -1;
6235 }
6236
6237 for (int i = 0; i < n; i++) {
6238 max = Math.max(max, layout.getLineWidth(i));
6239 }
6240
6241 return (int) FloatMath.ceil(max);
6242 }
6243
6244 /**
6245 * Set whether the TextView includes extra top and bottom padding to make
6246 * room for accents that go above the normal ascent and descent.
6247 * The default is true.
6248 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07006249 * @see #getIncludeFontPadding()
6250 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006251 * @attr ref android.R.styleable#TextView_includeFontPadding
6252 */
6253 public void setIncludeFontPadding(boolean includepad) {
Gilles Debunne22378292011-08-12 10:38:52 -07006254 if (mIncludePad != includepad) {
6255 mIncludePad = includepad;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006256
Gilles Debunne22378292011-08-12 10:38:52 -07006257 if (mLayout != null) {
6258 nullLayouts();
6259 requestLayout();
6260 invalidate();
6261 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006262 }
6263 }
6264
Gilles Debunnef03acef2012-04-30 19:26:19 -07006265 /**
6266 * Gets whether the TextView includes extra top and bottom padding to make
6267 * room for accents that go above the normal ascent and descent.
6268 *
6269 * @see #setIncludeFontPadding(boolean)
6270 *
6271 * @attr ref android.R.styleable#TextView_includeFontPadding
6272 */
6273 public boolean getIncludeFontPadding() {
6274 return mIncludePad;
6275 }
6276
Romain Guy4dc4f732009-06-19 15:16:40 -07006277 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006278
6279 @Override
6280 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6281 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6282 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6283 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6284 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6285
6286 int width;
6287 int height;
6288
6289 BoringLayout.Metrics boring = UNKNOWN_BORING;
6290 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6291
Doug Feltcb3791202011-07-07 11:57:48 -07006292 if (mTextDir == null) {
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07006293 mTextDir = getTextDirectionHeuristic();
Doug Feltcb3791202011-07-07 11:57:48 -07006294 }
6295
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006296 int des = -1;
6297 boolean fromexisting = false;
6298
6299 if (widthMode == MeasureSpec.EXACTLY) {
6300 // Parent has told us how big to be. So be it.
6301 width = widthSize;
6302 } else {
6303 if (mLayout != null && mEllipsize == null) {
6304 des = desired(mLayout);
6305 }
6306
6307 if (des < 0) {
Doug Feltcb3791202011-07-07 11:57:48 -07006308 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006309 if (boring != null) {
6310 mBoring = boring;
6311 }
6312 } else {
6313 fromexisting = true;
6314 }
6315
6316 if (boring == null || boring == UNKNOWN_BORING) {
6317 if (des < 0) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006318 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006319 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006320 width = des;
6321 } else {
6322 width = boring.width;
6323 }
6324
6325 final Drawables dr = mDrawables;
6326 if (dr != null) {
6327 width = Math.max(width, dr.mDrawableWidthTop);
6328 width = Math.max(width, dr.mDrawableWidthBottom);
6329 }
6330
6331 if (mHint != null) {
6332 int hintDes = -1;
6333 int hintWidth;
6334
Romain Guy4dc4f732009-06-19 15:16:40 -07006335 if (mHintLayout != null && mEllipsize == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006336 hintDes = desired(mHintLayout);
6337 }
6338
6339 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006340 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006341 if (hintBoring != null) {
6342 mHintBoring = hintBoring;
6343 }
6344 }
6345
6346 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6347 if (hintDes < 0) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006348 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006350 hintWidth = hintDes;
6351 } else {
6352 hintWidth = hintBoring.width;
6353 }
6354
6355 if (hintWidth > width) {
6356 width = hintWidth;
6357 }
6358 }
6359
6360 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6361
6362 if (mMaxWidthMode == EMS) {
6363 width = Math.min(width, mMaxWidth * getLineHeight());
6364 } else {
6365 width = Math.min(width, mMaxWidth);
6366 }
6367
6368 if (mMinWidthMode == EMS) {
6369 width = Math.max(width, mMinWidth * getLineHeight());
6370 } else {
6371 width = Math.max(width, mMinWidth);
6372 }
6373
6374 // Check against our minimum width
6375 width = Math.max(width, getSuggestedMinimumWidth());
6376
6377 if (widthMode == MeasureSpec.AT_MOST) {
6378 width = Math.min(widthSize, width);
6379 }
6380 }
6381
6382 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6383 int unpaddedWidth = want;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006384
Jeff Brown033a0012011-11-11 15:30:16 -08006385 if (mHorizontallyScrolling) want = VERY_WIDE;
Gilles Debunne9a80a652011-01-31 12:56:07 -08006386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006387 int hintWant = want;
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006388 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006389
6390 if (mLayout == null) {
6391 makeNewLayout(want, hintWant, boring, hintBoring,
Romain Guy4dc4f732009-06-19 15:16:40 -07006392 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006393 } else {
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07006394 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6395 (hintWidth != hintWant) ||
6396 (mLayout.getEllipsizedWidth() !=
6397 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6398
6399 final boolean widthChanged = (mHint == null) &&
6400 (mEllipsize == null) &&
6401 (want > mLayout.getWidth()) &&
6402 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6403
6404 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6405
6406 if (layoutChanged || maximumChanged) {
6407 if (!maximumChanged && widthChanged) {
6408 mLayout.increaseWidthTo(want);
6409 } else {
6410 makeNewLayout(want, hintWant, boring, hintBoring,
6411 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6412 }
6413 } else {
6414 // Nothing has changed
6415 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006416 }
6417
6418 if (heightMode == MeasureSpec.EXACTLY) {
6419 // Parent has told us how big to be. So be it.
6420 height = heightSize;
6421 mDesiredHeightAtMeasure = -1;
6422 } else {
6423 int desired = getDesiredHeight();
6424
6425 height = desired;
6426 mDesiredHeightAtMeasure = desired;
6427
6428 if (heightMode == MeasureSpec.AT_MOST) {
Christoffer Gurell1d05c7c2009-10-12 15:53:39 +02006429 height = Math.min(desired, heightSize);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006430 }
6431 }
6432
Romain Guy4dc4f732009-06-19 15:16:40 -07006433 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006434 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
Romain Guy4dc4f732009-06-19 15:16:40 -07006435 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006436 }
6437
6438 /*
6439 * We didn't let makeNewLayout() register to bring the cursor into view,
6440 * so do it here if there is any possibility that it is needed.
6441 */
6442 if (mMovement != null ||
6443 mLayout.getWidth() > unpaddedWidth ||
6444 mLayout.getHeight() > unpaddedHeight) {
6445 registerForPreDraw();
6446 } else {
6447 scrollTo(0, 0);
6448 }
6449
6450 setMeasuredDimension(width, height);
6451 }
6452
6453 private int getDesiredHeight() {
Romain Guy4dc4f732009-06-19 15:16:40 -07006454 return Math.max(
6455 getDesiredHeight(mLayout, true),
6456 getDesiredHeight(mHintLayout, mEllipsize != null));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006457 }
6458
6459 private int getDesiredHeight(Layout layout, boolean cap) {
6460 if (layout == null) {
6461 return 0;
6462 }
6463
6464 int linecount = layout.getLineCount();
6465 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6466 int desired = layout.getLineTop(linecount);
6467
6468 final Drawables dr = mDrawables;
6469 if (dr != null) {
6470 desired = Math.max(desired, dr.mDrawableHeightLeft);
6471 desired = Math.max(desired, dr.mDrawableHeightRight);
6472 }
6473
6474 desired += pad;
6475
6476 if (mMaxMode == LINES) {
6477 /*
6478 * Don't cap the hint to a certain number of lines.
6479 * (Do cap it, though, if we have a maximum pixel height.)
6480 */
6481 if (cap) {
6482 if (linecount > mMaximum) {
Gilles Debunne0a4db3c2011-01-14 12:12:04 -08006483 desired = layout.getLineTop(mMaximum);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006484
6485 if (dr != null) {
6486 desired = Math.max(desired, dr.mDrawableHeightLeft);
6487 desired = Math.max(desired, dr.mDrawableHeightRight);
6488 }
6489
6490 desired += pad;
6491 linecount = mMaximum;
6492 }
6493 }
6494 } else {
6495 desired = Math.min(desired, mMaximum);
6496 }
6497
6498 if (mMinMode == LINES) {
6499 if (linecount < mMinimum) {
6500 desired += getLineHeight() * (mMinimum - linecount);
6501 }
6502 } else {
6503 desired = Math.max(desired, mMinimum);
6504 }
6505
6506 // Check against our minimum height
6507 desired = Math.max(desired, getSuggestedMinimumHeight());
6508
6509 return desired;
6510 }
6511
6512 /**
6513 * Check whether a change to the existing text layout requires a
6514 * new view layout.
6515 */
6516 private void checkForResize() {
6517 boolean sizeChanged = false;
6518
6519 if (mLayout != null) {
6520 // Check if our width changed
6521 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6522 sizeChanged = true;
6523 invalidate();
6524 }
6525
6526 // Check if our height changed
6527 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6528 int desiredHeight = getDesiredHeight();
6529
6530 if (desiredHeight != this.getHeight()) {
6531 sizeChanged = true;
6532 }
Romain Guy980a9382010-01-08 15:06:28 -08006533 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006534 if (mDesiredHeightAtMeasure >= 0) {
6535 int desiredHeight = getDesiredHeight();
6536
6537 if (desiredHeight != mDesiredHeightAtMeasure) {
6538 sizeChanged = true;
6539 }
6540 }
6541 }
6542 }
6543
6544 if (sizeChanged) {
6545 requestLayout();
6546 // caller will have already invalidated
6547 }
6548 }
6549
6550 /**
6551 * Check whether entirely new text requires a new view layout
6552 * or merely a new text layout.
6553 */
6554 private void checkForRelayout() {
6555 // If we have a fixed width, we can just swap in a new text layout
6556 // if the text height stays the same or if the view height is fixed.
6557
6558 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6559 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6560 (mHint == null || mHintLayout != null) &&
6561 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6562 // Static width, so try making a new text layout.
6563
6564 int oldht = mLayout.getHeight();
6565 int want = mLayout.getWidth();
6566 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6567
6568 /*
6569 * No need to bring the text into view, since the size is not
6570 * changing (unless we do the requestLayout(), in which case it
6571 * will happen at measure).
6572 */
6573 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
Romain Guye1e0dc82009-11-03 17:21:04 -08006574 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6575 false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006576
Romain Guye1e0dc82009-11-03 17:21:04 -08006577 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6578 // In a fixed-height view, so use our new text layout.
6579 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
Romain Guy980a9382010-01-08 15:06:28 -08006580 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
Romain Guye1e0dc82009-11-03 17:21:04 -08006581 invalidate();
6582 return;
6583 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006584
Romain Guye1e0dc82009-11-03 17:21:04 -08006585 // Dynamic height, but height has stayed the same,
6586 // so use our new text layout.
6587 if (mLayout.getHeight() == oldht &&
6588 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6589 invalidate();
6590 return;
6591 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006592 }
6593
6594 // We lose: the height has changed and we have a dynamic height.
6595 // Request a new view layout using our new text layout.
6596 requestLayout();
6597 invalidate();
6598 } else {
6599 // Dynamic width, so we have no choice but to request a new
6600 // view layout with a new text layout.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006601 nullLayouts();
6602 requestLayout();
6603 invalidate();
6604 }
6605 }
6606
Gilles Debunne954325e2012-01-25 11:57:06 -08006607 @Override
6608 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6609 super.onLayout(changed, left, top, right, bottom);
Raph Levienf5c1a872012-10-15 17:22:26 -07006610 if (mDeferScroll >= 0) {
6611 int curs = mDeferScroll;
6612 mDeferScroll = -1;
Raph Levien8b179692012-10-16 14:32:47 -07006613 bringPointIntoView(Math.min(curs, mText.length()));
Raph Levienf5c1a872012-10-15 17:22:26 -07006614 }
Gilles Debunne954325e2012-01-25 11:57:06 -08006615 }
6616
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006617 private boolean isShowingHint() {
6618 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6619 }
6620
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006621 /**
6622 * Returns true if anything changed.
6623 */
6624 private boolean bringTextIntoView() {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006625 Layout layout = isShowingHint() ? mHintLayout : mLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006626 int line = 0;
6627 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006628 line = layout.getLineCount() - 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006629 }
6630
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006631 Layout.Alignment a = layout.getParagraphAlignment(line);
6632 int dir = layout.getParagraphDirection(line);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006633 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6634 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006635 int ht = layout.getHeight();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006636
6637 int scrollx, scrolly;
6638
Doug Felt25b9f422011-07-11 13:48:37 -07006639 // Convert to left, center, or right alignment.
6640 if (a == Layout.Alignment.ALIGN_NORMAL) {
6641 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6642 Layout.Alignment.ALIGN_RIGHT;
6643 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6644 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6645 Layout.Alignment.ALIGN_LEFT;
6646 }
6647
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006648 if (a == Layout.Alignment.ALIGN_CENTER) {
6649 /*
6650 * Keep centered if possible, or, if it is too wide to fit,
6651 * keep leading edge in view.
6652 */
6653
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006654 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6655 int right = (int) FloatMath.ceil(layout.getLineRight(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006656
6657 if (right - left < hspace) {
6658 scrollx = (right + left) / 2 - hspace / 2;
6659 } else {
6660 if (dir < 0) {
6661 scrollx = right - hspace;
6662 } else {
6663 scrollx = left;
6664 }
6665 }
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006666 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006667 int right = (int) FloatMath.ceil(layout.getLineRight(line));
Doug Felt25b9f422011-07-11 13:48:37 -07006668 scrollx = right - hspace;
Fabrice Di Megliod2b5d1c2011-07-13 19:38:17 -07006669 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006670 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006671 }
6672
6673 if (ht < vspace) {
6674 scrolly = 0;
6675 } else {
6676 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6677 scrolly = ht - vspace;
6678 } else {
6679 scrolly = 0;
6680 }
6681 }
6682
6683 if (scrollx != mScrollX || scrolly != mScrollY) {
6684 scrollTo(scrollx, scrolly);
6685 return true;
6686 } else {
6687 return false;
6688 }
6689 }
6690
6691 /**
6692 * Move the point, specified by the offset, into the view if it is needed.
6693 * This has to be called after layout. Returns true if anything changed.
6694 */
6695 public boolean bringPointIntoView(int offset) {
Raph Levienf5c1a872012-10-15 17:22:26 -07006696 if (isLayoutRequested()) {
6697 mDeferScroll = offset;
6698 return false;
6699 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006700 boolean changed = false;
6701
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006702 Layout layout = isShowingHint() ? mHintLayout: mLayout;
Gilles Debunne176ee3d2011-07-16 13:28:41 -07006703
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006704 if (layout == null) return changed;
6705
6706 int line = layout.getLineForOffset(offset);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006707
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006708 int grav;
6709
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006710 switch (layout.getParagraphAlignment(line)) {
Doug Felt25b9f422011-07-11 13:48:37 -07006711 case ALIGN_LEFT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006712 grav = 1;
6713 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006714 case ALIGN_RIGHT:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006715 grav = -1;
6716 break;
Doug Felt25b9f422011-07-11 13:48:37 -07006717 case ALIGN_NORMAL:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006718 grav = layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006719 break;
6720 case ALIGN_OPPOSITE:
Fabrice Di Megliob8634192011-11-28 18:44:47 -08006721 grav = -layout.getParagraphDirection(line);
Doug Felt25b9f422011-07-11 13:48:37 -07006722 break;
6723 case ALIGN_CENTER:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006724 default:
6725 grav = 0;
Doug Felt25b9f422011-07-11 13:48:37 -07006726 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006727 }
6728
Raph Levienafe8e9b2012-12-19 16:09:32 -08006729 // We only want to clamp the cursor to fit within the layout width
6730 // in left-to-right modes, because in a right to left alignment,
6731 // we want to scroll to keep the line-right on the screen, as other
6732 // lines are likely to have text flush with the right margin, which
6733 // we want to keep visible.
6734 // A better long-term solution would probably be to measure both
6735 // the full line and a blank-trimmed version, and, for example, use
6736 // the latter measurement for centering and right alignment, but for
6737 // the time being we only implement the cursor clamping in left to
6738 // right where it is most likely to be annoying.
6739 final boolean clamped = grav > 0;
6740 // FIXME: Is it okay to truncate this, or should we round?
6741 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
6742 final int top = layout.getLineTop(line);
6743 final int bottom = layout.getLineTop(line + 1);
6744
6745 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6746 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6747 int ht = layout.getHeight();
6748
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006749 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6750 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
Raph Levienafe8e9b2012-12-19 16:09:32 -08006751 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
6752 // If cursor has been clamped, make sure we don't scroll.
6753 right = Math.max(x, left + hspace);
6754 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006755
6756 int hslack = (bottom - top) / 2;
6757 int vslack = hslack;
6758
6759 if (vslack > vspace / 4)
6760 vslack = vspace / 4;
6761 if (hslack > hspace / 4)
6762 hslack = hspace / 4;
6763
6764 int hs = mScrollX;
6765 int vs = mScrollY;
6766
6767 if (top - vs < vslack)
6768 vs = top - vslack;
6769 if (bottom - vs > vspace - vslack)
6770 vs = bottom - (vspace - vslack);
6771 if (ht - vs < vspace)
6772 vs = ht - vspace;
6773 if (0 - vs > 0)
6774 vs = 0;
6775
6776 if (grav != 0) {
6777 if (x - hs < hslack) {
6778 hs = x - hslack;
6779 }
6780 if (x - hs > hspace - hslack) {
6781 hs = x - (hspace - hslack);
6782 }
6783 }
6784
6785 if (grav < 0) {
6786 if (left - hs > 0)
6787 hs = left;
6788 if (right - hs < hspace)
6789 hs = right - hspace;
6790 } else if (grav > 0) {
6791 if (right - hs < hspace)
6792 hs = right - hspace;
6793 if (left - hs > 0)
6794 hs = left;
6795 } else /* grav == 0 */ {
6796 if (right - left <= hspace) {
6797 /*
6798 * If the entire text fits, center it exactly.
6799 */
6800 hs = left - (hspace - (right - left)) / 2;
6801 } else if (x > right - hslack) {
6802 /*
6803 * If we are near the right edge, keep the right edge
6804 * at the edge of the view.
6805 */
6806 hs = right - hspace;
6807 } else if (x < left + hslack) {
6808 /*
6809 * If we are near the left edge, keep the left edge
6810 * at the edge of the view.
6811 */
6812 hs = left;
6813 } else if (left > hs) {
6814 /*
6815 * Is there whitespace visible at the left? Fix it if so.
6816 */
6817 hs = left;
6818 } else if (right < hs + hspace) {
6819 /*
6820 * Is there whitespace visible at the right? Fix it if so.
6821 */
6822 hs = right - hspace;
6823 } else {
6824 /*
6825 * Otherwise, float as needed.
6826 */
6827 if (x - hs < hslack) {
6828 hs = x - hslack;
6829 }
6830 if (x - hs > hspace - hslack) {
6831 hs = x - (hspace - hslack);
6832 }
6833 }
6834 }
6835
6836 if (hs != mScrollX || vs != mScrollY) {
6837 if (mScroller == null) {
6838 scrollTo(hs, vs);
6839 } else {
6840 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
6841 int dx = hs - mScrollX;
6842 int dy = vs - mScrollY;
6843
6844 if (duration > ANIMATED_SCROLL_GAP) {
6845 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
Mike Cleronf116bf82009-09-27 19:14:12 -07006846 awakenScrollBars(mScroller.getDuration());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006847 invalidate();
6848 } else {
6849 if (!mScroller.isFinished()) {
6850 mScroller.abortAnimation();
6851 }
6852
6853 scrollBy(dx, dy);
6854 }
6855
6856 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
6857 }
6858
6859 changed = true;
6860 }
6861
6862 if (isFocused()) {
Gilles Debunne716dbf62011-03-07 18:12:10 -08006863 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
6864 // requestRectangleOnScreen() is in terms of content coordinates.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006865
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006866 // The offsets here are to ensure the rectangle we are using is
6867 // within our view bounds, in case the cursor is on the far left
6868 // or right. If it isn't withing the bounds, then this request
6869 // will be ignored.
Gilles Debunne60e21862012-01-30 15:04:14 -08006870 if (mTempRect == null) mTempRect = new Rect();
Dianne Hackborn70a3f672011-08-08 14:32:41 -07006871 mTempRect.set(x - 2, top, x + 2, bottom);
Gilles Debunne716dbf62011-03-07 18:12:10 -08006872 getInterestingRect(mTempRect, line);
6873 mTempRect.offset(mScrollX, mScrollY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006874
Gilles Debunne716dbf62011-03-07 18:12:10 -08006875 if (requestRectangleOnScreen(mTempRect)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006876 changed = true;
6877 }
6878 }
6879
6880 return changed;
6881 }
6882
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006883 /**
6884 * Move the cursor, if needed, so that it is at an offset that is visible
6885 * to the user. This will not move the cursor if it represents more than
6886 * one character (a selection range). This will only work if the
6887 * TextView contains spannable text; otherwise it will do nothing.
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07006888 *
Gilles Debunne57f4e5b2010-06-21 16:21:51 -07006889 * @return True if the cursor was actually moved, false otherwise.
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006890 */
6891 public boolean moveCursorToVisibleOffset() {
6892 if (!(mText instanceof Spannable)) {
6893 return false;
6894 }
Gilles Debunne05336272010-07-09 20:13:45 -07006895 int start = getSelectionStart();
6896 int end = getSelectionEnd();
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006897 if (start != end) {
6898 return false;
6899 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006900
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006901 // First: make sure the line is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006902
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006903 int line = mLayout.getLineForOffset(start);
6904
6905 final int top = mLayout.getLineTop(line);
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006906 final int bottom = mLayout.getLineTop(line + 1);
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006907 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6908 int vslack = (bottom - top) / 2;
6909 if (vslack > vspace / 4)
6910 vslack = vspace / 4;
6911 final int vs = mScrollY;
6912
6913 if (top < (vs+vslack)) {
6914 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
6915 } else if (bottom > (vspace+vs-vslack)) {
6916 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
6917 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006918
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006919 // Next: make sure the character is visible on screen:
Gilles Debunne2d373a12012-04-20 15:32:19 -07006920
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006921 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6922 final int hs = mScrollX;
6923 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
6924 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
Gilles Debunne2d373a12012-04-20 15:32:19 -07006925
Doug Feltc982f602010-05-25 11:51:40 -07006926 // line might contain bidirectional text
6927 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
6928 final int highChar = leftChar > rightChar ? leftChar : rightChar;
6929
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006930 int newStart = start;
Doug Feltc982f602010-05-25 11:51:40 -07006931 if (newStart < lowChar) {
6932 newStart = lowChar;
6933 } else if (newStart > highChar) {
6934 newStart = highChar;
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006935 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006936
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006937 if (newStart != start) {
6938 Selection.setSelection((Spannable)mText, newStart);
6939 return true;
6940 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07006941
Dianne Hackborn38e98fc2009-03-24 18:18:24 -07006942 return false;
6943 }
6944
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006945 @Override
6946 public void computeScroll() {
6947 if (mScroller != null) {
6948 if (mScroller.computeScrollOffset()) {
6949 mScrollX = mScroller.getCurrX();
6950 mScrollY = mScroller.getCurrY();
Romain Guy0fd89bf2011-01-26 15:41:30 -08006951 invalidateParentCaches();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006952 postInvalidate(); // So we draw again
6953 }
6954 }
6955 }
6956
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07006957 private void getInterestingRect(Rect r, int line) {
6958 convertFromViewportToContentCoordinates(r);
6959
6960 // Rectangle can can be expanded on first and last line to take
6961 // padding into account.
6962 // TODO Take left/right padding into account too?
6963 if (line == 0) r.top -= getExtendedPaddingTop();
6964 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
6965 }
6966
6967 private void convertFromViewportToContentCoordinates(Rect r) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006968 final int horizontalOffset = viewportToContentHorizontalOffset();
6969 r.left += horizontalOffset;
6970 r.right += horizontalOffset;
6971
6972 final int verticalOffset = viewportToContentVerticalOffset();
6973 r.top += verticalOffset;
6974 r.bottom += verticalOffset;
6975 }
6976
Gilles Debunned88876a2012-03-16 17:34:04 -07006977 int viewportToContentHorizontalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006978 return getCompoundPaddingLeft() - mScrollX;
6979 }
6980
Gilles Debunned88876a2012-03-16 17:34:04 -07006981 int viewportToContentVerticalOffset() {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006982 int offset = getExtendedPaddingTop() - mScrollY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006983 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006984 offset += getVerticalOffset(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006985 }
Gilles Debunne44c1e4c2010-09-08 19:33:20 -07006986 return offset;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08006987 }
6988
6989 @Override
6990 public void debug(int depth) {
6991 super.debug(depth);
6992
6993 String output = debugIndent(depth);
6994 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
6995 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
6996 + "} ";
6997
6998 if (mText != null) {
6999
7000 output += "mText=\"" + mText + "\" ";
7001 if (mLayout != null) {
7002 output += "mLayout width=" + mLayout.getWidth()
7003 + " height=" + mLayout.getHeight();
7004 }
7005 } else {
7006 output += "mText=NULL";
7007 }
7008 Log.d(VIEW_LOG_TAG, output);
7009 }
7010
7011 /**
7012 * Convenience for {@link Selection#getSelectionStart}.
7013 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007014 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007015 public int getSelectionStart() {
7016 return Selection.getSelectionStart(getText());
7017 }
7018
7019 /**
7020 * Convenience for {@link Selection#getSelectionEnd}.
7021 */
Konstantin Lopyrevbea95162010-08-10 17:02:18 -07007022 @ViewDebug.ExportedProperty(category = "text")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007023 public int getSelectionEnd() {
7024 return Selection.getSelectionEnd(getText());
7025 }
7026
7027 /**
7028 * Return true iff there is a selection inside this text view.
7029 */
7030 public boolean hasSelection() {
Gilles Debunne03789e82010-09-07 19:07:17 -07007031 final int selectionStart = getSelectionStart();
7032 final int selectionEnd = getSelectionEnd();
7033
7034 return selectionStart >= 0 && selectionStart != selectionEnd;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007035 }
7036
7037 /**
7038 * Sets the properties of this field (lines, horizontally scrolling,
7039 * transformation method) to be for a single-line input.
7040 *
7041 * @attr ref android.R.styleable#TextView_singleLine
7042 */
7043 public void setSingleLine() {
7044 setSingleLine(true);
7045 }
7046
7047 /**
Adam Powell7f8f79a2011-07-07 18:35:54 -07007048 * Sets the properties of this field to transform input to ALL CAPS
7049 * display. This may use a "small caps" formatting if available.
7050 * This setting will be ignored if this field is editable or selectable.
7051 *
7052 * This call replaces the current transformation method. Disabling this
7053 * will not necessarily restore the previous behavior from before this
7054 * was enabled.
7055 *
7056 * @see #setTransformationMethod(TransformationMethod)
7057 * @attr ref android.R.styleable#TextView_textAllCaps
7058 */
7059 public void setAllCaps(boolean allCaps) {
7060 if (allCaps) {
7061 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7062 } else {
7063 setTransformationMethod(null);
7064 }
7065 }
7066
7067 /**
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007068 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7069 * transformation method) to be for a single-line input; if false, restores these to the default
7070 * conditions.
7071 *
7072 * Note that the default conditions are not necessarily those that were in effect prior this
7073 * method, and you may want to reset these properties to your custom values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007074 *
7075 * @attr ref android.R.styleable#TextView_singleLine
7076 */
7077 @android.view.RemotableViewMethod
7078 public void setSingleLine(boolean singleLine) {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007079 // Could be used, but may break backward compatibility.
7080 // if (mSingleLine == singleLine) return;
Gilles Debunned7483bf2010-11-10 10:47:45 -08007081 setInputTypeSingleLine(singleLine);
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007082 applySingleLine(singleLine, true, true);
Gilles Debunned7483bf2010-11-10 10:47:45 -08007083 }
7084
7085 /**
7086 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7087 * @param singleLine
7088 */
7089 private void setInputTypeSingleLine(boolean singleLine) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007090 if (mEditor != null &&
7091 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007092 if (singleLine) {
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 } else {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007095 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007096 }
7097 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007098 }
7099
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007100 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7101 boolean changeMaxLines) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007102 mSingleLine = singleLine;
7103 if (singleLine) {
7104 setLines(1);
7105 setHorizontallyScrolling(true);
7106 if (applyTransformation) {
Gilles Debunne91a08cf2010-11-08 17:34:49 -08007107 setTransformationMethod(SingleLineTransformationMethod.getInstance());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007108 }
7109 } else {
Gilles Debunnea3ae4a02010-12-13 17:22:39 -08007110 if (changeMaxLines) {
7111 setMaxLines(Integer.MAX_VALUE);
7112 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007113 setHorizontallyScrolling(false);
7114 if (applyTransformation) {
7115 setTransformationMethod(null);
7116 }
7117 }
7118 }
Gilles Debunneb2316962010-12-21 17:32:43 -08007119
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007120 /**
7121 * Causes words in the text that are longer than the view is wide
7122 * to be ellipsized instead of broken in the middle. You may also
7123 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
Kenny Roote855d132009-06-11 11:00:42 -05007124 * to constrain the text to a single line. Use <code>null</code>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007125 * to turn off ellipsizing.
7126 *
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007127 * If {@link #setMaxLines} has been used to set two or more lines,
Gilles Debunne6435a562011-08-04 21:22:30 -07007128 * {@link android.text.TextUtils.TruncateAt#END} and
7129 * {@link android.text.TextUtils.TruncateAt#MARQUEE}* are only supported
7130 * (other ellipsizing types will not do anything).
Fabrice Di Meglio8059e0902011-08-10 16:31:58 -07007131 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007132 * @attr ref android.R.styleable#TextView_ellipsize
7133 */
7134 public void setEllipsize(TextUtils.TruncateAt where) {
Gilles Debunne22378292011-08-12 10:38:52 -07007135 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7136 if (mEllipsize != where) {
7137 mEllipsize = where;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007138
Gilles Debunne22378292011-08-12 10:38:52 -07007139 if (mLayout != null) {
7140 nullLayouts();
7141 requestLayout();
7142 invalidate();
7143 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007144 }
7145 }
7146
7147 /**
7148 * Sets how many times to repeat the marquee animation. Only applied if the
7149 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7150 *
Gilles Debunnef03acef2012-04-30 19:26:19 -07007151 * @see #getMarqueeRepeatLimit()
7152 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007153 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7154 */
7155 public void setMarqueeRepeatLimit(int marqueeLimit) {
7156 mMarqueeRepeatLimit = marqueeLimit;
7157 }
7158
7159 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007160 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7161 * TextView has marquee enabled.
7162 *
7163 * @return the number of times the marquee animation is repeated. -1 if the animation
7164 * repeats indefinitely
7165 *
7166 * @see #setMarqueeRepeatLimit(int)
7167 *
7168 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7169 */
7170 public int getMarqueeRepeatLimit() {
7171 return mMarqueeRepeatLimit;
7172 }
7173
7174 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007175 * Returns where, if anywhere, words that are longer than the view
7176 * is wide should be ellipsized.
7177 */
7178 @ViewDebug.ExportedProperty
7179 public TextUtils.TruncateAt getEllipsize() {
7180 return mEllipsize;
7181 }
7182
7183 /**
7184 * Set the TextView so that when it takes focus, all the text is
7185 * selected.
7186 *
7187 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7188 */
7189 @android.view.RemotableViewMethod
7190 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07007191 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007192 mEditor.mSelectAllOnFocus = selectAllOnFocus;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007193
7194 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7195 setText(mText, BufferType.SPANNABLE);
7196 }
7197 }
7198
7199 /**
Gilles Debunnef03acef2012-04-30 19:26:19 -07007200 * Set whether the cursor is visible. The default is true. Note that this property only
7201 * makes sense for editable TextView.
7202 *
7203 * @see #isCursorVisible()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007204 *
7205 * @attr ref android.R.styleable#TextView_cursorVisible
7206 */
7207 @android.view.RemotableViewMethod
7208 public void setCursorVisible(boolean visible) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007209 if (visible && mEditor == null) return; // visible is the default value with no edit data
Gilles Debunne5fae9962012-05-08 14:53:20 -07007210 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007211 if (mEditor.mCursorVisible != visible) {
7212 mEditor.mCursorVisible = visible;
Gilles Debunne3d010062011-02-18 14:16:41 -08007213 invalidate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007214
Gilles Debunne2d373a12012-04-20 15:32:19 -07007215 mEditor.makeBlink();
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007216
Gilles Debunne3d010062011-02-18 14:16:41 -08007217 // InsertionPointCursorController depends on mCursorVisible
Gilles Debunne2d373a12012-04-20 15:32:19 -07007218 mEditor.prepareCursorControllers();
Gilles Debunne3d010062011-02-18 14:16:41 -08007219 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007220 }
7221
Gilles Debunnef03acef2012-04-30 19:26:19 -07007222 /**
7223 * @return whether or not the cursor is visible (assuming this TextView is editable)
7224 *
7225 * @see #setCursorVisible(boolean)
7226 *
7227 * @attr ref android.R.styleable#TextView_cursorVisible
7228 */
7229 public boolean isCursorVisible() {
7230 // true is the default value
7231 return mEditor == null ? true : mEditor.mCursorVisible;
7232 }
7233
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007234 private boolean canMarquee() {
7235 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
Adam Powell282e3772011-08-30 16:51:11 -07007236 return width > 0 && (mLayout.getLineWidth(0) > width ||
7237 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7238 mSavedMarqueeModeLayout.getLineWidth(0) > width));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007239 }
7240
7241 private void startMarquee() {
Romain Guy4dc4f732009-06-19 15:16:40 -07007242 // Do not ellipsize EditText
Gilles Debunne60e21862012-01-30 15:04:14 -08007243 if (getKeyListener() != null) return;
Romain Guy4dc4f732009-06-19 15:16:40 -07007244
Romain Guy939151f2009-04-08 14:22:40 -07007245 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7246 return;
7247 }
7248
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007249 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7250 getLineCount() == 1 && canMarquee()) {
Romain Guy939151f2009-04-08 14:22:40 -07007251
Adam Powell282e3772011-08-30 16:51:11 -07007252 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7253 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7254 final Layout tmp = mLayout;
7255 mLayout = mSavedMarqueeModeLayout;
7256 mSavedMarqueeModeLayout = tmp;
7257 setHorizontalFadingEdgeEnabled(true);
7258 requestLayout();
7259 invalidate();
7260 }
7261
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007262 if (mMarquee == null) mMarquee = new Marquee(this);
7263 mMarquee.start(mMarqueeRepeatLimit);
7264 }
7265 }
7266
7267 private void stopMarquee() {
7268 if (mMarquee != null && !mMarquee.isStopped()) {
7269 mMarquee.stop();
7270 }
Adam Powell282e3772011-08-30 16:51:11 -07007271
7272 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7273 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7274 final Layout tmp = mSavedMarqueeModeLayout;
7275 mSavedMarqueeModeLayout = mLayout;
7276 mLayout = tmp;
7277 setHorizontalFadingEdgeEnabled(false);
7278 requestLayout();
7279 invalidate();
7280 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007281 }
7282
7283 private void startStopMarquee(boolean start) {
7284 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7285 if (start) {
7286 startMarquee();
7287 } else {
7288 stopMarquee();
7289 }
7290 }
7291 }
7292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007293 /**
Gilles Debunne4469e602011-03-09 14:38:04 -08007294 * This method is called when the text is changed, in case any subclasses
7295 * would like to know.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007296 *
Gilles Debunne4469e602011-03-09 14:38:04 -08007297 * Within <code>text</code>, the <code>lengthAfter</code> characters
7298 * beginning at <code>start</code> have just replaced old text that had
7299 * length <code>lengthBefore</code>. It is an error to attempt to make
7300 * changes to <code>text</code> from this callback.
7301 *
7302 * @param text The text the TextView is displaying
7303 * @param start The offset of the start of the range of the text that was
7304 * modified
7305 * @param lengthBefore The length of the former text that has been replaced
7306 * @param lengthAfter The length of the replacement modified text
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007307 */
Gilles Debunne4469e602011-03-09 14:38:04 -08007308 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007309 // intentionally empty, template pattern method can be overridden by subclasses
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007310 }
7311
7312 /**
7313 * This method is called when the selection has changed, in case any
7314 * subclasses would like to know.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007315 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007316 * @param selStart The new selection start location.
7317 * @param selEnd The new selection end location.
7318 */
7319 protected void onSelectionChanged(int selStart, int selEnd) {
Svetoslav Ganova0156172011-06-26 17:55:44 -07007320 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007321 }
Svetoslav Ganova0156172011-06-26 17:55:44 -07007322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007323 /**
7324 * Adds a TextWatcher to the list of those whose methods are called
7325 * whenever this TextView's text changes.
7326 * <p>
7327 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7328 * not called after {@link #setText} calls. Now, doing {@link #setText}
7329 * if there are any text changed listeners forces the buffer type to
7330 * Editable if it would not otherwise be and does call this method.
7331 */
7332 public void addTextChangedListener(TextWatcher watcher) {
7333 if (mListeners == null) {
7334 mListeners = new ArrayList<TextWatcher>();
7335 }
7336
7337 mListeners.add(watcher);
7338 }
7339
7340 /**
7341 * Removes the specified TextWatcher from the list of those whose
7342 * methods are called
7343 * whenever this TextView's text changes.
7344 */
7345 public void removeTextChangedListener(TextWatcher watcher) {
7346 if (mListeners != null) {
7347 int i = mListeners.indexOf(watcher);
7348
7349 if (i >= 0) {
7350 mListeners.remove(i);
7351 }
7352 }
7353 }
7354
Gilles Debunne6435a562011-08-04 21:22:30 -07007355 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007356 if (mListeners != null) {
7357 final ArrayList<TextWatcher> list = mListeners;
7358 final int count = list.size();
7359 for (int i = 0; i < count; i++) {
7360 list.get(i).beforeTextChanged(text, start, before, after);
7361 }
7362 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007363
7364 // The spans that are inside or intersect the modified region no longer make sense
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007365 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7366 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
Gilles Debunne6435a562011-08-04 21:22:30 -07007367 }
7368
7369 // Removes all spans that are inside or actually overlap the start..end range
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007370 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
Gilles Debunne6435a562011-08-04 21:22:30 -07007371 if (!(mText instanceof Editable)) return;
7372 Editable text = (Editable) mText;
7373
7374 T[] spans = text.getSpans(start, end, type);
7375 final int length = spans.length;
7376 for (int i = 0; i < length; i++) {
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007377 final int spanStart = text.getSpanStart(spans[i]);
7378 final int spanEnd = text.getSpanEnd(spans[i]);
7379 if (spanEnd == start || spanStart == end) break;
Gilles Debunne6435a562011-08-04 21:22:30 -07007380 text.removeSpan(spans[i]);
7381 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007382 }
7383
Satoshi Kataokad7429c12013-06-05 16:30:23 +09007384 void removeAdjacentSuggestionSpans(final int pos) {
7385 if (!(mText instanceof Editable)) return;
7386 final Editable text = (Editable) mText;
7387
7388 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7389 final int length = spans.length;
7390 for (int i = 0; i < length; i++) {
7391 final int spanStart = text.getSpanStart(spans[i]);
7392 final int spanEnd = text.getSpanEnd(spans[i]);
7393 if (spanEnd == pos || spanStart == pos) {
7394 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7395 text.removeSpan(spans[i]);
7396 }
7397 }
7398 }
7399 }
7400
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007401 /**
7402 * Not private so it can be called from an inner class without going
7403 * through a thunk.
7404 */
Gilles Debunne6435a562011-08-04 21:22:30 -07007405 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007406 if (mListeners != null) {
7407 final ArrayList<TextWatcher> list = mListeners;
7408 final int count = list.size();
7409 for (int i = 0; i < count; i++) {
7410 list.get(i).onTextChanged(text, start, before, after);
7411 }
7412 }
Gilles Debunne1a22db22011-11-20 22:13:21 +01007413
Gilles Debunne2d373a12012-04-20 15:32:19 -07007414 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007415 }
7416
7417 /**
7418 * Not private so it can be called from an inner class without going
7419 * through a thunk.
7420 */
7421 void sendAfterTextChanged(Editable text) {
7422 if (mListeners != null) {
7423 final ArrayList<TextWatcher> list = mListeners;
7424 final int count = list.size();
7425 for (int i = 0; i < count; i++) {
7426 list.get(i).afterTextChanged(text);
7427 }
7428 }
7429 }
7430
Gilles Debunned88876a2012-03-16 17:34:04 -07007431 void updateAfterEdit() {
7432 invalidate();
7433 int curs = getSelectionStart();
7434
7435 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7436 registerForPreDraw();
7437 }
7438
Raph Levienf5c1a872012-10-15 17:22:26 -07007439 checkForResize();
7440
Gilles Debunned88876a2012-03-16 17:34:04 -07007441 if (curs >= 0) {
7442 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007443 if (mEditor != null) mEditor.makeBlink();
Gilles Debunned88876a2012-03-16 17:34:04 -07007444 bringPointIntoView(curs);
7445 }
Gilles Debunned88876a2012-03-16 17:34:04 -07007446 }
7447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007448 /**
7449 * Not private so it can be called from an inner class without going
7450 * through a thunk.
7451 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007452 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007453 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007454 if (ims == null || ims.mBatchEditNesting == 0) {
7455 updateAfterEdit();
7456 }
7457 if (ims != null) {
7458 ims.mContentChanged = true;
7459 if (ims.mChangedStart < 0) {
7460 ims.mChangedStart = start;
7461 ims.mChangedEnd = start+before;
7462 } else {
Viktor Yakovel964be412010-02-17 08:35:57 +01007463 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7464 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007465 }
7466 ims.mChangedDelta += after-before;
7467 }
Gilles Debunne186aaf92011-09-16 14:26:12 -07007468
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007469 sendOnTextChanged(buffer, start, before, after);
7470 onTextChanged(buffer, start, before, after);
7471 }
Gilles Debunne60e21862012-01-30 15:04:14 -08007472
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007473 /**
7474 * Not private so it can be called from an inner class without going
7475 * through a thunk.
7476 */
Gilles Debunnecf9cf2f2010-12-08 17:43:58 -08007477 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007478 // XXX Make the start and end move together if this ends up
7479 // spending too much time invalidating.
7480
7481 boolean selChanged = false;
7482 int newSelStart=-1, newSelEnd=-1;
Gilles Debunne60e21862012-01-30 15:04:14 -08007483
Gilles Debunne2d373a12012-04-20 15:32:19 -07007484 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08007485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007486 if (what == Selection.SELECTION_END) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007487 selChanged = true;
7488 newSelEnd = newStart;
7489
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007490 if (oldStart >= 0 || newStart >= 0) {
7491 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
Raph Levienf5c1a872012-10-15 17:22:26 -07007492 checkForResize();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007493 registerForPreDraw();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007494 if (mEditor != null) mEditor.makeBlink();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007495 }
7496 }
7497
7498 if (what == Selection.SELECTION_START) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007499 selChanged = true;
7500 newSelStart = newStart;
7501
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007502 if (oldStart >= 0 || newStart >= 0) {
7503 int end = Selection.getSelectionEnd(buf);
7504 invalidateCursor(end, oldStart, newStart);
7505 }
7506 }
7507
7508 if (selChanged) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007509 mHighlightPathBogus = true;
Gilles Debunne2d373a12012-04-20 15:32:19 -07007510 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
Gilles Debunne60e21862012-01-30 15:04:14 -08007511
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007512 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7513 if (newSelStart < 0) {
7514 newSelStart = Selection.getSelectionStart(buf);
7515 }
7516 if (newSelEnd < 0) {
7517 newSelEnd = Selection.getSelectionEnd(buf);
7518 }
7519 onSelectionChanged(newSelStart, newSelEnd);
7520 }
7521 }
Gilles Debunne8615ac92011-11-29 15:25:03 -08007522
Gilles Debunneb35ab7b2011-12-05 15:54:00 -08007523 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7524 what instanceof CharacterStyle) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007525 if (ims == null || ims.mBatchEditNesting == 0) {
7526 invalidate();
Gilles Debunne83051b82012-02-24 20:01:13 -08007527 mHighlightPathBogus = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007528 checkForResize();
7529 } else {
7530 ims.mContentChanged = true;
7531 }
Gilles Debunneebc86af2012-04-20 15:10:47 -07007532 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007533 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7534 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
Gilles Debunneebc86af2012-04-20 15:10:47 -07007535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007536 }
7537
7538 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
Gilles Debunne83051b82012-02-24 20:01:13 -08007539 mHighlightPathBogus = true;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007540 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7541 ims.mSelectionModeChanged = true;
7542 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007543
7544 if (Selection.getSelectionStart(buf) >= 0) {
7545 if (ims == null || ims.mBatchEditNesting == 0) {
7546 invalidateCursor();
7547 } else {
7548 ims.mCursorChanged = true;
7549 }
7550 }
7551 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007553 if (what instanceof ParcelableSpan) {
7554 // If this is a span that can be sent to a remote process,
7555 // the current extract editor would be interested in it.
Gilles Debunnec62589c2012-04-12 14:50:23 -07007556 if (ims != null && ims.mExtractedTextRequest != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007557 if (ims.mBatchEditNesting != 0) {
7558 if (oldStart >= 0) {
7559 if (ims.mChangedStart > oldStart) {
7560 ims.mChangedStart = oldStart;
7561 }
7562 if (ims.mChangedStart > oldEnd) {
7563 ims.mChangedStart = oldEnd;
7564 }
7565 }
7566 if (newStart >= 0) {
7567 if (ims.mChangedStart > newStart) {
7568 ims.mChangedStart = newStart;
7569 }
7570 if (ims.mChangedStart > newEnd) {
7571 ims.mChangedStart = newEnd;
7572 }
7573 }
7574 } else {
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007575 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007576 + oldStart + "-" + oldEnd + ","
Gilles Debunnec62589c2012-04-12 14:50:23 -07007577 + newStart + "-" + newEnd + " " + what);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007578 ims.mContentChanged = true;
7579 }
7580 }
7581 }
Gilles Debunne6435a562011-08-04 21:22:30 -07007582
Gilles Debunne2d373a12012-04-20 15:32:19 -07007583 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7584 what instanceof SpellCheckSpan) {
Gilles Debunne69865bd2012-05-09 11:12:03 -07007585 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
Gilles Debunne6435a562011-08-04 21:22:30 -07007586 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007587 }
7588
Gilles Debunne6435a562011-08-04 21:22:30 -07007589 /**
Romain Guydcc490f2010-02-24 17:59:35 -08007590 * @hide
7591 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007592 @Override
Romain Guya440b002010-02-24 15:57:54 -08007593 public void dispatchFinishTemporaryDetach() {
7594 mDispatchTemporaryDetach = true;
7595 super.dispatchFinishTemporaryDetach();
7596 mDispatchTemporaryDetach = false;
7597 }
7598
7599 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007600 public void onStartTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007601 super.onStartTemporaryDetach();
7602 // Only track when onStartTemporaryDetach() is called directly,
7603 // usually because this instance is an editable field in a list
7604 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
Gilles Debunne4b2274f2011-02-25 15:18:03 -08007605
Adam Powell057a5852012-05-11 10:28:38 -07007606 // Tell the editor that we are temporarily detached. It can use this to preserve
7607 // selection state as needed.
7608 if (mEditor != null) mEditor.mTemporaryDetach = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007609 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007611 @Override
7612 public void onFinishTemporaryDetach() {
Romain Guya440b002010-02-24 15:57:54 -08007613 super.onFinishTemporaryDetach();
7614 // Only track when onStartTemporaryDetach() is called directly,
7615 // usually because this instance is an editable field in a list
7616 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
Adam Powell057a5852012-05-11 10:28:38 -07007617 if (mEditor != null) mEditor.mTemporaryDetach = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007618 }
Gilles Debunne3784a7f2011-07-15 13:49:38 -07007619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007620 @Override
7621 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7622 if (mTemporaryDetach) {
7623 // If we are temporarily in the detach state, then do nothing.
7624 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7625 return;
7626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007627
Gilles Debunne2d373a12012-04-20 15:32:19 -07007628 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
Gilles Debunne03789e82010-09-07 19:07:17 -07007629
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007630 if (focused) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007631 if (mText instanceof Spannable) {
7632 Spannable sp = (Spannable) mText;
7633 MetaKeyKeyListener.resetMetaState(sp);
7634 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007635 }
7636
7637 startStopMarquee(focused);
7638
7639 if (mTransformation != null) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007640 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007641 }
7642
7643 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7644 }
7645
7646 @Override
7647 public void onWindowFocusChanged(boolean hasWindowFocus) {
7648 super.onWindowFocusChanged(hasWindowFocus);
7649
Gilles Debunne2d373a12012-04-20 15:32:19 -07007650 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007651
7652 startStopMarquee(hasWindowFocus);
7653 }
7654
Adam Powellba0a2c32010-09-28 17:41:23 -07007655 @Override
7656 protected void onVisibilityChanged(View changedView, int visibility) {
7657 super.onVisibilityChanged(changedView, visibility);
Gilles Debunne60e21862012-01-30 15:04:14 -08007658 if (mEditor != null && visibility != VISIBLE) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007659 mEditor.hideControllers();
Adam Powellba0a2c32010-09-28 17:41:23 -07007660 }
7661 }
7662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007663 /**
7664 * Use {@link BaseInputConnection#removeComposingSpans
7665 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7666 * state from this text view.
7667 */
7668 public void clearComposingText() {
7669 if (mText instanceof Spannable) {
7670 BaseInputConnection.removeComposingSpans((Spannable)mText);
7671 }
7672 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007674 @Override
7675 public void setSelected(boolean selected) {
7676 boolean wasSelected = isSelected();
7677
7678 super.setSelected(selected);
7679
7680 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7681 if (selected) {
7682 startMarquee();
7683 } else {
7684 stopMarquee();
7685 }
7686 }
7687 }
7688
7689 @Override
7690 public boolean onTouchEvent(MotionEvent event) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07007691 final int action = event.getActionMasked();
Adam Powell965b9692010-10-21 18:44:32 -07007692
Gilles Debunne2d373a12012-04-20 15:32:19 -07007693 if (mEditor != null) mEditor.onTouchEvent(event);
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007694
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007695 final boolean superResult = super.onTouchEvent(event);
7696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007697 /*
7698 * Don't handle the release after a long press, because it will
7699 * move the selection away from whatever the menu action was
7700 * trying to affect.
7701 */
Gilles Debunne2d373a12012-04-20 15:32:19 -07007702 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7703 mEditor.mDiscardNextActionUp = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007704 return superResult;
7705 }
7706
Gilles Debunne70a63122011-09-01 13:27:33 -07007707 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07007708 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
Gilles Debunnec3e85a72011-01-21 08:46:06 -08007709
Gilles Debunne70a63122011-09-01 13:27:33 -07007710 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
Janos Levai042856c2010-10-15 02:53:58 +03007711 && mText instanceof Spannable && mLayout != null) {
Gilles Debunnef788a9f2010-07-22 10:17:23 -07007712 boolean handled = false;
7713
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -07007714 if (mMovement != null) {
7715 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007717
Gilles Debunne60e21862012-01-30 15:04:14 -08007718 final boolean textIsSelectable = isTextSelectable();
7719 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007720 // The LinkMovementMethod which should handle taps on links has not been installed
Gilles Debunne70a63122011-09-01 13:27:33 -07007721 // on non editable text that support text selection.
7722 // We reproduce its behavior here to open links for these.
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007723 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7724 getSelectionEnd(), ClickableSpan.class);
7725
Gilles Debunne822b8f02012-01-17 18:02:15 -08007726 if (links.length > 0) {
Gilles Debunnef3895ed2010-12-21 12:53:58 -08007727 links[0].onClick(this);
7728 handled = true;
7729 }
7730 }
7731
Gilles Debunne60e21862012-01-30 15:04:14 -08007732 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007733 // Show the IME, except when selecting in read-only text.
satok863fcd62011-06-21 17:38:02 +09007734 final InputMethodManager imm = InputMethodManager.peekInstance();
satoka67a3cf2011-09-07 17:14:03 +09007735 viewClicked(imm);
Gilles Debunne3473b2b2012-04-20 16:21:10 -07007736 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
Gilles Debunne180bb1b2011-03-10 11:14:00 -08007737 handled |= imm != null && imm.showSoftInput(this, 0);
Adam Powell879fb6b2010-09-20 11:23:56 -07007738 }
Gilles Debunnebbb5d6e2010-06-21 16:21:51 -07007739
Gilles Debunned88876a2012-03-16 17:34:04 -07007740 // The above condition ensures that the mEditor is not null
Gilles Debunne2d373a12012-04-20 15:32:19 -07007741 mEditor.onTouchUpEvent(event);
Gilles Debunne6435a562011-08-04 21:22:30 -07007742
7743 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007744 }
7745
The Android Open Source Project4df24232009-03-05 14:34:35 -08007746 if (handled) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007747 return true;
7748 }
7749 }
7750
7751 return superResult;
7752 }
7753
Jeff Brown8f345672011-02-26 13:29:53 -08007754 @Override
7755 public boolean onGenericMotionEvent(MotionEvent event) {
7756 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
7757 try {
7758 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
7759 return true;
7760 }
7761 } catch (AbstractMethodError ex) {
7762 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
7763 // Ignore its absence in case third party applications implemented the
7764 // interface directly.
7765 }
7766 }
7767 return super.onGenericMotionEvent(event);
7768 }
7769
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007770 /**
Gilles Debunne86b9c782010-11-11 10:43:48 -08007771 * @return True iff this TextView contains a text that can be edited, or if this is
7772 * a selectable TextView.
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007773 */
Gilles Debunned88876a2012-03-16 17:34:04 -07007774 boolean isTextEditable() {
Gilles Debunnef076eeb2010-11-29 11:32:53 -08007775 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
Gilles Debunnecc3ec6c2010-06-23 10:30:27 -07007776 }
7777
The Android Open Source Project4df24232009-03-05 14:34:35 -08007778 /**
7779 * Returns true, only while processing a touch gesture, if the initial
7780 * 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 -07007781 * its selection changed. Only valid while processing the touch gesture
Gilles Debunne053c4392012-03-15 15:35:26 -07007782 * of interest, in an editable text view.
The Android Open Source Project4df24232009-03-05 14:34:35 -08007783 */
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007784 public boolean didTouchFocusSelect() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007785 return mEditor != null && mEditor.mTouchFocusSelected;
The Android Open Source Project4df24232009-03-05 14:34:35 -08007786 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07007787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007788 @Override
7789 public void cancelLongPress() {
7790 super.cancelLongPress();
Gilles Debunne2d373a12012-04-20 15:32:19 -07007791 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007792 }
Gilles Debunne70a63122011-09-01 13:27:33 -07007793
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007794 @Override
7795 public boolean onTrackballEvent(MotionEvent event) {
Gilles Debunne60e21862012-01-30 15:04:14 -08007796 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007797 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
7798 return true;
7799 }
7800 }
7801
7802 return super.onTrackballEvent(event);
7803 }
7804
7805 public void setScroller(Scroller s) {
7806 mScroller = s;
7807 }
7808
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007809 @Override
7810 protected float getLeftFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007811 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7812 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007813 if (mMarquee != null && !mMarquee.isStopped()) {
7814 final Marquee marquee = mMarquee;
Romain Guyc2303192009-04-03 17:37:18 -07007815 if (marquee.shouldDrawLeftFade()) {
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007816 final float scroll = marquee.getScroll();
7817 return scroll / getHorizontalFadingEdgeLength();
Romain Guyc2303192009-04-03 17:37:18 -07007818 } else {
7819 return 0.0f;
7820 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007821 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007822 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007823 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007824 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007825 case Gravity.LEFT:
7826 return 0.0f;
7827 case Gravity.RIGHT:
7828 return (mLayout.getLineRight(0) - (mRight - mLeft) -
7829 getCompoundPaddingLeft() - getCompoundPaddingRight() -
7830 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
7831 case Gravity.CENTER_HORIZONTAL:
7832 return 0.0f;
7833 }
7834 }
7835 }
7836 return super.getLeftFadingEdgeStrength();
7837 }
7838
7839 @Override
7840 protected float getRightFadingEdgeStrength() {
Adam Powell282e3772011-08-30 16:51:11 -07007841 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
7842 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007843 if (mMarquee != null && !mMarquee.isStopped()) {
7844 final Marquee marquee = mMarquee;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07007845 final float maxFadeScroll = marquee.getMaxFadeScroll();
7846 final float scroll = marquee.getScroll();
7847 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007848 } else if (getLineCount() == 1) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07007849 final int layoutDirection = getLayoutDirection();
Fabrice Di Meglioc0053222011-06-13 12:16:51 -07007850 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
Fabrice Di Meglio6a036402011-05-23 14:43:23 -07007851 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007852 case Gravity.LEFT:
Romain Guy076dc9f2009-06-24 17:17:51 -07007853 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
7854 getCompoundPaddingRight();
7855 final float lineWidth = mLayout.getLineWidth(0);
7856 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007857 case Gravity.RIGHT:
7858 return 0.0f;
7859 case Gravity.CENTER_HORIZONTAL:
Gilles Debunne44c14732010-10-19 11:56:59 -07007860 case Gravity.FILL_HORIZONTAL:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007861 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
7862 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
7863 getHorizontalFadingEdgeLength();
7864 }
7865 }
7866 }
7867 return super.getRightFadingEdgeStrength();
7868 }
7869
7870 @Override
7871 protected int computeHorizontalScrollRange() {
Romain Guydac5f9f2010-07-08 11:40:54 -07007872 if (mLayout != null) {
7873 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
7874 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
7875 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007876
7877 return super.computeHorizontalScrollRange();
7878 }
7879
7880 @Override
7881 protected int computeVerticalScrollRange() {
7882 if (mLayout != null)
7883 return mLayout.getHeight();
7884
7885 return super.computeVerticalScrollRange();
7886 }
7887
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07007888 @Override
7889 protected int computeVerticalScrollExtent() {
7890 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
7891 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007892
7893 @Override
Svetoslav Ganovea515ae2011-09-14 18:15:32 -07007894 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
7895 super.findViewsWithText(outViews, searched, flags);
7896 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
7897 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
7898 String searchedLowerCase = searched.toString().toLowerCase();
7899 String textLowerCase = mText.toString().toLowerCase();
7900 if (textLowerCase.contains(searchedLowerCase)) {
7901 outViews.add(this);
7902 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07007903 }
7904 }
7905
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007906 public enum BufferType {
7907 NORMAL, SPANNABLE, EDITABLE,
7908 }
7909
7910 /**
7911 * Returns the TextView_textColor attribute from the
John Spurlock330dd532012-12-18 12:03:11 -05007912 * TypedArray, if set, or the TextAppearance_textColor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007913 * from the TextView_textAppearance attribute, if TextView_textColor
7914 * was not set directly.
7915 */
7916 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
7917 ColorStateList colors;
7918 colors = attrs.getColorStateList(com.android.internal.R.styleable.
7919 TextView_textColor);
7920
7921 if (colors == null) {
7922 int ap = attrs.getResourceId(com.android.internal.R.styleable.
7923 TextView_textAppearance, -1);
7924 if (ap != -1) {
7925 TypedArray appearance;
7926 appearance = context.obtainStyledAttributes(ap,
7927 com.android.internal.R.styleable.TextAppearance);
7928 colors = appearance.getColorStateList(com.android.internal.R.styleable.
7929 TextAppearance_textColor);
7930 appearance.recycle();
7931 }
7932 }
7933
7934 return colors;
7935 }
7936
7937 /**
7938 * Returns the default color from the TextView_textColor attribute
7939 * from the AttributeSet, if set, or the default color from the
7940 * TextAppearance_textColor from the TextView_textAppearance attribute,
7941 * if TextView_textColor was not set directly.
7942 */
7943 public static int getTextColor(Context context,
7944 TypedArray attrs,
7945 int def) {
7946 ColorStateList colors = getTextColors(context, attrs);
7947
7948 if (colors == null) {
7949 return def;
7950 } else {
7951 return colors.getDefaultColor();
7952 }
7953 }
7954
7955 @Override
7956 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Jeff Brownc1df9072010-12-21 16:38:50 -08007957 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
7958 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
7959 switch (keyCode) {
7960 case KeyEvent.KEYCODE_A:
7961 if (canSelectText()) {
7962 return onTextContextMenuItem(ID_SELECT_ALL);
7963 }
7964 break;
7965 case KeyEvent.KEYCODE_X:
7966 if (canCut()) {
7967 return onTextContextMenuItem(ID_CUT);
7968 }
7969 break;
7970 case KeyEvent.KEYCODE_C:
7971 if (canCopy()) {
7972 return onTextContextMenuItem(ID_COPY);
7973 }
7974 break;
7975 case KeyEvent.KEYCODE_V:
7976 if (canPaste()) {
7977 return onTextContextMenuItem(ID_PASTE);
7978 }
7979 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007980 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007981 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007982 return super.onKeyShortcut(keyCode, event);
7983 }
7984
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007985 /**
7986 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
7987 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
Gilles Debunne2d373a12012-04-20 15:32:19 -07007988 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
7989 * sufficient.
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007990 */
Gilles Debunnebaaace52010-10-01 15:47:13 -07007991 private boolean canSelectText() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07007992 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007993 }
7994
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007995 /**
7996 * Test based on the <i>intrinsic</i> charateristics of the TextView.
7997 * The text must be spannable and the movement method must allow for arbitary selection.
Gilles Debunne2d373a12012-04-20 15:32:19 -07007998 *
Gilles Debunnecbcb3452010-12-17 15:31:02 -08007999 * See also {@link #canSelectText()}.
8000 */
Gilles Debunned88876a2012-03-16 17:34:04 -07008001 boolean textCanBeSelected() {
Gilles Debunne05336272010-07-09 20:13:45 -07008002 // prepareCursorController() relies on this method.
8003 // If you change this condition, make sure prepareCursorController is called anywhere
8004 // the value of this condition might be changed.
Gilles Debunnebb588da2011-07-11 18:26:19 -07008005 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008006 return isTextEditable() ||
8007 (isTextSelectable() && mText instanceof Spannable && isEnabled());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008008 }
8009
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008010 private Locale getTextServicesLocale(boolean allowNullLocale) {
8011 // Start fetching the text services locale asynchronously.
8012 updateTextServicesLocaleAsync();
8013 // If !allowNullLocale and there is no cached text services locale, just return the default
8014 // locale.
8015 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8016 : mCurrentSpellCheckerLocaleCache;
8017 }
8018
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008019 /**
8020 * This is a temporary method. Future versions may support multi-locale text.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008021 * Caveat: This method may not return the latest text services locale, but this should be
8022 * acceptable and it's more important to make this method asynchronous.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008023 *
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008024 * @return The locale that should be used for a word iterator
satok05f24702011-11-02 19:29:35 +09008025 * in this TextView, based on the current spell checker settings,
8026 * the current IME's locale, or the system default locale.
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008027 * Please note that a word iterator in this TextView is different from another word iterator
8028 * used by SpellChecker.java of TextView. This method should be used for the former.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008029 * @hide
8030 */
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008031 // TODO: Support multi-locale
8032 // TODO: Update the text services locale immediately after the keyboard locale is switched
8033 // by catching intent of keyboard switch event
satok05f24702011-11-02 19:29:35 +09008034 public Locale getTextServicesLocale() {
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008035 return getTextServicesLocale(false /* allowNullLocale */);
8036 }
8037
8038 /**
8039 * This is a temporary method. Future versions may support multi-locale text.
8040 * Caveat: This method may not return the latest spell checker locale, but this should be
8041 * acceptable and it's more important to make this method asynchronous.
8042 *
8043 * @return The locale that should be used for a spell checker in this TextView,
8044 * based on the current spell checker settings, the current IME's locale, or the system default
8045 * locale.
8046 * @hide
8047 */
8048 public Locale getSpellCheckerLocale() {
8049 return getTextServicesLocale(true /* allowNullLocale */);
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008050 }
8051
8052 private void updateTextServicesLocaleAsync() {
Romain Guy31f05442013-06-03 14:19:54 -07008053 // AsyncTask.execute() uses a serial executor which means we don't have
8054 // to lock around updateTextServicesLocaleLocked() to prevent it from
8055 // being executed n times in parallel.
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008056 AsyncTask.execute(new Runnable() {
8057 @Override
8058 public void run() {
Romain Guy31f05442013-06-03 14:19:54 -07008059 updateTextServicesLocaleLocked();
Satoshi Kataoka1eac6b72012-10-09 17:34:04 +09008060 }
8061 });
8062 }
8063
8064 private void updateTextServicesLocaleLocked() {
satok05f24702011-11-02 19:29:35 +09008065 final TextServicesManager textServicesManager = (TextServicesManager)
8066 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8067 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008068 final Locale locale;
satok05f24702011-11-02 19:29:35 +09008069 if (subtype != null) {
satokf927e172012-05-24 16:52:54 +09008070 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008071 } else {
8072 locale = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008073 }
Satoshi Kataoka5bb4ee6d2012-12-05 22:25:48 +09008074 mCurrentSpellCheckerLocaleCache = locale;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008075 }
8076
8077 void onLocaleChanged() {
8078 // Will be re-created on demand in getWordIterator with the proper new locale
Gilles Debunne2d373a12012-04-20 15:32:19 -07008079 mEditor.mWordIterator = null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008080 }
8081
8082 /**
Gilles Debunned88876a2012-03-16 17:34:04 -07008083 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8084 * Made available to achieve a consistent behavior.
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008085 * @hide
8086 */
8087 public WordIterator getWordIterator() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008088 if (mEditor != null) {
8089 return mEditor.getWordIterator();
Gilles Debunned88876a2012-03-16 17:34:04 -07008090 } else {
8091 return null;
Gilles Debunne9d8d3f12011-10-13 12:15:10 -07008092 }
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008093 }
Gilles Debunnedf4ee432010-08-25 19:13:48 -07008094
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008095 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -07008096 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov887e1a12011-04-29 15:09:28 -07008097 super.onPopulateAccessibilityEvent(event);
8098
Svetoslav Ganov1d1e1102010-11-16 16:44:03 -08008099 final boolean isPassword = hasPasswordTransformationMethod();
alanv7d624192012-05-21 14:23:17 -07008100 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8101 final CharSequence text = getTextForAccessibility();
Svetoslav Ganovd37848a2011-09-20 14:03:55 -07008102 if (!TextUtils.isEmpty(text)) {
svetoslavganov75986cf2009-05-14 22:28:01 -07008103 event.getText().add(text);
8104 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008105 }
svetoslavganov75986cf2009-05-14 22:28:01 -07008106 }
8107
alanv7d624192012-05-21 14:23:17 -07008108 /**
8109 * @return true if the user has explicitly allowed accessibility services
8110 * to speak passwords.
8111 */
8112 private boolean shouldSpeakPasswordsForAccessibility() {
8113 return (Settings.Secure.getInt(mContext.getContentResolver(),
8114 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1);
8115 }
8116
Svetoslav Ganov30401322011-05-12 18:53:45 -07008117 @Override
8118 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8119 super.onInitializeAccessibilityEvent(event);
8120
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008121 event.setClassName(TextView.class.getName());
Svetoslav Ganov30401322011-05-12 18:53:45 -07008122 final boolean isPassword = hasPasswordTransformationMethod();
8123 event.setPassword(isPassword);
Svetoslav Ganova0156172011-06-26 17:55:44 -07008124
8125 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8126 event.setFromIndex(Selection.getSelectionStart(mText));
8127 event.setToIndex(Selection.getSelectionEnd(mText));
8128 event.setItemCount(mText.length());
8129 }
Svetoslav Ganov30401322011-05-12 18:53:45 -07008130 }
8131
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008132 @Override
8133 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8134 super.onInitializeAccessibilityNodeInfo(info);
8135
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008136 info.setClassName(TextView.class.getName());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008137 final boolean isPassword = hasPasswordTransformationMethod();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08008138 info.setPassword(isPassword);
8139
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008140 if (!isPassword) {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008141 info.setText(getTextForAccessibility());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008142 }
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008143
Svetoslavbcc46a02013-02-06 11:56:00 -08008144 if (mBufferType == BufferType.EDITABLE) {
8145 info.setEditable(true);
8146 }
8147
Svetoslav6254f482013-06-04 17:22:14 -07008148 if (mEditor != null) {
8149 info.setInputType(mEditor.mInputType);
8150 }
8151
Svetoslavdb7da0e2013-04-22 18:34:02 -07008152 if (!TextUtils.isEmpty(mText)) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008153 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8154 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8155 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8156 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8157 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8158 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8159 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8160 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008161
Svetoslav7c512842013-01-30 23:02:08 -08008162 if (isFocused()) {
8163 if (canSelectText()) {
8164 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8165 }
8166 if (canCopy()) {
8167 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8168 }
8169 if (canPaste()) {
8170 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8171 }
8172 if (canCut()) {
8173 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8174 }
8175 }
8176 }
8177
8178 @Override
8179 public boolean performAccessibilityAction(int action, Bundle arguments) {
8180 switch (action) {
8181 case AccessibilityNodeInfo.ACTION_COPY: {
8182 if (isFocused() && canCopy()) {
8183 if (onTextContextMenuItem(ID_COPY)) {
Svetoslav7c512842013-01-30 23:02:08 -08008184 return true;
8185 }
8186 }
8187 } return false;
8188 case AccessibilityNodeInfo.ACTION_PASTE: {
8189 if (isFocused() && canPaste()) {
8190 if (onTextContextMenuItem(ID_PASTE)) {
Svetoslav7c512842013-01-30 23:02:08 -08008191 return true;
8192 }
8193 }
8194 } return false;
8195 case AccessibilityNodeInfo.ACTION_CUT: {
8196 if (isFocused() && canCut()) {
8197 if (onTextContextMenuItem(ID_CUT)) {
Svetoslav7c512842013-01-30 23:02:08 -08008198 return true;
8199 }
8200 }
8201 } return false;
8202 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8203 if (isFocused() && canSelectText()) {
Svetoslav7c512842013-01-30 23:02:08 -08008204 CharSequence text = getIterableTextForAccessibility();
8205 if (text == null) {
8206 return false;
8207 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008208 final int start = (arguments != null) ? arguments.getInt(
8209 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8210 final int end = (arguments != null) ? arguments.getInt(
8211 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8212 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8213 // No arguments clears the selection.
8214 if (start == end && end == -1) {
8215 Selection.removeSelection((Spannable) text);
Svetoslavd0c83cc2013-02-04 18:39:59 -08008216 return true;
Svetoslav7c512842013-01-30 23:02:08 -08008217 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008218 if (start >= 0 && start <= end && end <= text.length()) {
8219 Selection.setSelection((Spannable) text, start, end);
8220 // Make sure selection mode is engaged.
8221 if (mEditor != null) {
8222 mEditor.startSelectionActionMode();
8223 }
Svetoslavd0c83cc2013-02-04 18:39:59 -08008224 return true;
8225 }
Svetoslav7c512842013-01-30 23:02:08 -08008226 }
8227 }
8228 } return false;
8229 default: {
8230 return super.performAccessibilityAction(action, arguments);
8231 }
8232 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07008233 }
8234
Svetoslav Ganov4e03f592011-07-29 22:17:14 -07008235 @Override
8236 public void sendAccessibilityEvent(int eventType) {
8237 // Do not send scroll events since first they are not interesting for
8238 // accessibility and second such events a generated too frequently.
8239 // For details see the implementation of bringTextIntoView().
8240 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8241 return;
8242 }
8243 super.sendAccessibilityEvent(eventType);
8244 }
8245
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008246 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008247 * Gets the text reported for accessibility purposes.
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008248 *
8249 * @return The accessibility text.
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008250 *
8251 * @hide
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008252 */
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008253 public CharSequence getTextForAccessibility() {
Svetoslav Ganovab5a40572011-09-15 11:30:01 -07008254 CharSequence text = getText();
8255 if (TextUtils.isEmpty(text)) {
8256 text = getHint();
8257 }
8258 return text;
8259 }
8260
svetoslavganov75986cf2009-05-14 22:28:01 -07008261 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8262 int fromIndex, int removedCount, int addedCount) {
8263 AccessibilityEvent event =
8264 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8265 event.setFromIndex(fromIndex);
8266 event.setRemovedCount(removedCount);
8267 event.setAddedCount(addedCount);
8268 event.setBeforeText(beforeText);
8269 sendAccessibilityEventUnchecked(event);
8270 }
8271
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008272 /**
8273 * Returns whether this text view is a current input method target. The
8274 * default implementation just checks with {@link InputMethodManager}.
8275 */
8276 public boolean isInputMethodTarget() {
8277 InputMethodManager imm = InputMethodManager.peekInstance();
8278 return imm != null && imm.isActive(this);
8279 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008280
Gilles Debunned88876a2012-03-16 17:34:04 -07008281 static final int ID_SELECT_ALL = android.R.id.selectAll;
8282 static final int ID_CUT = android.R.id.cut;
8283 static final int ID_COPY = android.R.id.copy;
8284 static final int ID_PASTE = android.R.id.paste;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008286 /**
8287 * Called when a context menu option for the text view is selected. Currently
Gilles Debunne07194e52011-11-02 14:18:44 -07008288 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8289 * {@link android.R.id#copy} or {@link android.R.id#paste}.
Gilles Debunnec59269f2011-04-22 11:46:09 -07008290 *
8291 * @return true if the context menu item action was performed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008292 */
8293 public boolean onTextContextMenuItem(int id) {
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008294 int min = 0;
8295 int max = mText.length();
Gilles Debunne64e54a62010-09-07 19:07:17 -07008296
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008297 if (isFocused()) {
Gilles Debunne64e54a62010-09-07 19:07:17 -07008298 final int selStart = getSelectionStart();
8299 final int selEnd = getSelectionEnd();
8300
Gilles Debunneb0d6ba12010-08-17 20:01:42 -07008301 min = Math.max(0, Math.min(selStart, selEnd));
8302 max = Math.max(0, Math.max(selStart, selEnd));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008303 }
8304
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008305 switch (id) {
Jeff Brownc1df9072010-12-21 16:38:50 -08008306 case ID_SELECT_ALL:
Gilles Debunne299733e2011-02-07 17:11:41 -08008307 // This does not enter text selection mode. Text is highlighted, so that it can be
Gilles Debunnec59269f2011-04-22 11:46:09 -07008308 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
Gilles Debunned88876a2012-03-16 17:34:04 -07008309 selectAllText();
Jeff Brownc1df9072010-12-21 16:38:50 -08008310 return true;
8311
8312 case ID_PASTE:
8313 paste(min, max);
8314 return true;
8315
8316 case ID_CUT:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008317 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Gilles Debunne39ba6d92011-11-09 05:26:26 +01008318 deleteText_internal(min, max);
Jeff Brownc1df9072010-12-21 16:38:50 -08008319 stopSelectionActionMode();
8320 return true;
8321
8322 case ID_COPY:
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008323 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
Jeff Brownc1df9072010-12-21 16:38:50 -08008324 stopSelectionActionMode();
8325 return true;
8326 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008327 return false;
8328 }
8329
Gilles Debunned88876a2012-03-16 17:34:04 -07008330 CharSequence getTransformedText(int start, int end) {
Gilles Debunnecf68fee2011-09-29 10:55:36 -07008331 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8332 }
8333
Gilles Debunnee15b3582010-06-16 15:17:21 -07008334 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008335 public boolean performLongClick() {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008336 boolean handled = false;
Gilles Debunnee28454a2011-09-07 18:03:44 -07008337
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008338 if (super.performLongClick()) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008339 handled = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008340 }
Gilles Debunnef170a342010-11-11 11:08:59 -08008341
Gilles Debunned88876a2012-03-16 17:34:04 -07008342 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008343 handled |= mEditor.performLongClick(handled);
Gilles Debunnee28454a2011-09-07 18:03:44 -07008344 }
8345
Gilles Debunne9f102ca2012-02-28 11:15:54 -08008346 if (handled) {
Gilles Debunnee28454a2011-09-07 18:03:44 -07008347 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Gilles Debunne2d373a12012-04-20 15:32:19 -07008348 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
Gilles Debunnef788a9f2010-07-22 10:17:23 -07008349 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008350
Gilles Debunne299733e2011-02-07 17:11:41 -08008351 return handled;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008352 }
8353
Gilles Debunne60e21862012-01-30 15:04:14 -08008354 @Override
8355 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8356 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
Gilles Debunne6382ade2012-02-29 15:22:32 -08008357 if (mEditor != null) {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008358 mEditor.onScrollChanged();
Gilles Debunne60e21862012-01-30 15:04:14 -08008359 }
8360 }
8361
8362 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008363 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8364 * by the IME or by the spell checker as the user types. This is done by adding
8365 * {@link SuggestionSpan}s to the text.
8366 *
8367 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8368 * user asks for them on these parts of the text. This value depends on the inputType of this
8369 * TextView.
8370 *
8371 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8372 *
8373 * In addition, the type variation must be one of
8374 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8375 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8376 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8377 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8378 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8379 *
8380 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8381 *
8382 * @return true if the suggestions popup window is enabled, based on the inputType.
8383 */
8384 public boolean isSuggestionsEnabled() {
8385 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008386 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8387 return false;
8388 }
8389 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008390
Gilles Debunne2d373a12012-04-20 15:32:19 -07008391 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
Gilles Debunne60e21862012-01-30 15:04:14 -08008392 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8393 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8394 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8395 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8396 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8397 }
8398
8399 /**
8400 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8401 * selection is initiated in this View.
8402 *
8403 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8404 * Paste actions, depending on what this View supports.
8405 *
8406 * A custom implementation can add new entries in the default menu in its
8407 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8408 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8409 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8410 * or {@link android.R.id#paste} ids as parameters.
8411 *
8412 * Returning false from
8413 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8414 * the action mode from being started.
8415 *
8416 * Action click events should be handled by the custom implementation of
8417 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8418 *
8419 * Note that text selection mode is not started when a TextView receives focus and the
8420 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8421 * that case, to allow for quick replacement.
8422 */
8423 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
Gilles Debunne5fae9962012-05-08 14:53:20 -07008424 createEditorIfNeeded();
Gilles Debunne2d373a12012-04-20 15:32:19 -07008425 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008426 }
8427
8428 /**
8429 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8430 *
8431 * @return The current custom selection callback.
8432 */
8433 public ActionMode.Callback getCustomSelectionActionModeCallback() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008434 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
Gilles Debunne60e21862012-01-30 15:04:14 -08008435 }
8436
8437 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008438 * @hide
8439 */
8440 protected void stopSelectionActionMode() {
Gilles Debunne2d373a12012-04-20 15:32:19 -07008441 mEditor.stopSelectionActionMode();
Gilles Debunned88876a2012-03-16 17:34:04 -07008442 }
8443
8444 boolean canCut() {
8445 if (hasPasswordTransformationMethod()) {
8446 return false;
Gilles Debunne60e21862012-01-30 15:04:14 -08008447 }
Gilles Debunned88876a2012-03-16 17:34:04 -07008448
Gilles Debunne2d373a12012-04-20 15:32:19 -07008449 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8450 mEditor.mKeyListener != null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008451 return true;
8452 }
8453
8454 return false;
8455 }
8456
8457 boolean canCopy() {
8458 if (hasPasswordTransformationMethod()) {
8459 return false;
8460 }
8461
8462 if (mText.length() > 0 && hasSelection()) {
8463 return true;
8464 }
8465
8466 return false;
8467 }
8468
8469 boolean canPaste() {
8470 return (mText instanceof Editable &&
Gilles Debunne2d373a12012-04-20 15:32:19 -07008471 mEditor != null && mEditor.mKeyListener != null &&
Gilles Debunned88876a2012-03-16 17:34:04 -07008472 getSelectionStart() >= 0 &&
8473 getSelectionEnd() >= 0 &&
8474 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8475 hasPrimaryClip());
8476 }
8477
8478 boolean selectAllText() {
8479 final int length = mText.length();
8480 Selection.setSelection((Spannable) mText, 0, length);
8481 return length > 0;
8482 }
8483
8484 /**
8485 * Prepare text so that there are not zero or two spaces at beginning and end of region defined
8486 * by [min, max] when replacing this region by paste.
8487 * Note that if there were two spaces (or more) at that position before, they are kept. We just
8488 * make sure we do not add an extra one from the paste content.
8489 */
8490 long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
8491 if (paste.length() > 0) {
8492 if (min > 0) {
8493 final char charBefore = mTransformed.charAt(min - 1);
8494 final char charAfter = paste.charAt(0);
8495
8496 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8497 // Two spaces at beginning of paste: remove one
8498 final int originalLength = mText.length();
8499 deleteText_internal(min - 1, min);
8500 // Due to filters, there is no guarantee that exactly one character was
8501 // removed: count instead.
8502 final int delta = mText.length() - originalLength;
8503 min += delta;
8504 max += delta;
8505 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8506 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8507 // No space at beginning of paste: add one
8508 final int originalLength = mText.length();
8509 replaceText_internal(min, min, " ");
8510 // Taking possible filters into account as above.
8511 final int delta = mText.length() - originalLength;
8512 min += delta;
8513 max += delta;
8514 }
8515 }
8516
8517 if (max < mText.length()) {
8518 final char charBefore = paste.charAt(paste.length() - 1);
8519 final char charAfter = mTransformed.charAt(max);
8520
8521 if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) {
8522 // Two spaces at end of paste: remove one
8523 deleteText_internal(max, max + 1);
8524 } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' &&
8525 !Character.isSpaceChar(charAfter) && charAfter != '\n') {
8526 // No space at end of paste: add one
8527 replaceText_internal(max, max, " ");
8528 }
8529 }
8530 }
8531
8532 return TextUtils.packRangeInLong(min, max);
Gilles Debunne60e21862012-01-30 15:04:14 -08008533 }
8534
8535 /**
8536 * Paste clipboard content between min and max positions.
8537 */
8538 private void paste(int min, int max) {
8539 ClipboardManager clipboard =
8540 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8541 ClipData clip = clipboard.getPrimaryClip();
8542 if (clip != null) {
8543 boolean didFirst = false;
8544 for (int i=0; i<clip.getItemCount(); i++) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07008545 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
Gilles Debunne60e21862012-01-30 15:04:14 -08008546 if (paste != null) {
8547 if (!didFirst) {
8548 long minMax = prepareSpacesAroundPaste(min, max, paste);
Gilles Debunne6c488de2012-03-01 16:20:35 -08008549 min = TextUtils.unpackRangeStartFromLong(minMax);
8550 max = TextUtils.unpackRangeEndFromLong(minMax);
Gilles Debunne60e21862012-01-30 15:04:14 -08008551 Selection.setSelection((Spannable) mText, max);
8552 ((Editable) mText).replace(min, max, paste);
8553 didFirst = true;
8554 } else {
8555 ((Editable) mText).insert(getSelectionEnd(), "\n");
8556 ((Editable) mText).insert(getSelectionEnd(), paste);
8557 }
8558 }
8559 }
8560 stopSelectionActionMode();
8561 LAST_CUT_OR_COPY_TIME = 0;
8562 }
8563 }
8564
8565 private void setPrimaryClip(ClipData clip) {
8566 ClipboardManager clipboard = (ClipboardManager) getContext().
8567 getSystemService(Context.CLIPBOARD_SERVICE);
8568 clipboard.setPrimaryClip(clip);
8569 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8570 }
8571
Gilles Debunne60e21862012-01-30 15:04:14 -08008572 /**
8573 * Get the character offset closest to the specified absolute position. A typical use case is to
8574 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8575 *
8576 * @param x The horizontal absolute position of a point on screen
8577 * @param y The vertical absolute position of a point on screen
8578 * @return the character offset for the character whose position is closest to the specified
8579 * position. Returns -1 if there is no layout.
8580 */
8581 public int getOffsetForPosition(float x, float y) {
8582 if (getLayout() == null) return -1;
8583 final int line = getLineAtCoordinate(y);
8584 final int offset = getOffsetAtCoordinate(line, x);
8585 return offset;
8586 }
8587
Gilles Debunned88876a2012-03-16 17:34:04 -07008588 float convertToLocalHorizontalCoordinate(float x) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008589 x -= getTotalPaddingLeft();
8590 // Clamp the position to inside of the view.
8591 x = Math.max(0.0f, x);
8592 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8593 x += getScrollX();
8594 return x;
8595 }
8596
Gilles Debunned88876a2012-03-16 17:34:04 -07008597 int getLineAtCoordinate(float y) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008598 y -= getTotalPaddingTop();
8599 // Clamp the position to inside of the view.
8600 y = Math.max(0.0f, y);
8601 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8602 y += getScrollY();
8603 return getLayout().getLineForVertical((int) y);
8604 }
8605
8606 private int getOffsetAtCoordinate(int line, float x) {
8607 x = convertToLocalHorizontalCoordinate(x);
8608 return getLayout().getOffsetForHorizontal(line, x);
8609 }
8610
Gilles Debunne60e21862012-01-30 15:04:14 -08008611 @Override
8612 public boolean onDragEvent(DragEvent event) {
8613 switch (event.getAction()) {
8614 case DragEvent.ACTION_DRAG_STARTED:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008615 return mEditor != null && mEditor.hasInsertionController();
Gilles Debunne60e21862012-01-30 15:04:14 -08008616
8617 case DragEvent.ACTION_DRAG_ENTERED:
8618 TextView.this.requestFocus();
8619 return true;
8620
8621 case DragEvent.ACTION_DRAG_LOCATION:
8622 final int offset = getOffsetForPosition(event.getX(), event.getY());
8623 Selection.setSelection((Spannable)mText, offset);
8624 return true;
8625
8626 case DragEvent.ACTION_DROP:
Gilles Debunne2d373a12012-04-20 15:32:19 -07008627 if (mEditor != null) mEditor.onDrop(event);
Gilles Debunne60e21862012-01-30 15:04:14 -08008628 return true;
8629
8630 case DragEvent.ACTION_DRAG_ENDED:
8631 case DragEvent.ACTION_DRAG_EXITED:
8632 default:
8633 return true;
8634 }
8635 }
8636
Gilles Debunne60e21862012-01-30 15:04:14 -08008637 boolean isInBatchEditMode() {
8638 if (mEditor == null) return false;
Gilles Debunne2d373a12012-04-20 15:32:19 -07008639 final Editor.InputMethodState ims = mEditor.mInputMethodState;
Gilles Debunne60e21862012-01-30 15:04:14 -08008640 if (ims != null) {
8641 return ims.mBatchEditNesting > 0;
8642 }
Gilles Debunne2d373a12012-04-20 15:32:19 -07008643 return mEditor.mInBatchEditControllers;
Gilles Debunne60e21862012-01-30 15:04:14 -08008644 }
8645
Fabrice Di Meglioa423f502013-05-14 13:20:32 -07008646 @Override
8647 public void onRtlPropertiesChanged(int layoutDirection) {
8648 super.onRtlPropertiesChanged(layoutDirection);
8649
8650 mTextDir = getTextDirectionHeuristic();
8651 }
8652
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008653 TextDirectionHeuristic getTextDirectionHeuristic() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008654 if (hasPasswordTransformationMethod()) {
Fabrice Di Meglio8701bb92012-11-14 19:57:11 -08008655 // passwords fields should be LTR
8656 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008657 }
8658
8659 // Always need to resolve layout direction first
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -07008660 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
Gilles Debunne60e21862012-01-30 15:04:14 -08008661
8662 // Now, we can select the heuristic
Fabrice Di Meglio97e146c2012-09-23 15:45:16 -07008663 switch (getTextDirection()) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008664 default:
8665 case TEXT_DIRECTION_FIRST_STRONG:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008666 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
Gilles Debunne60e21862012-01-30 15:04:14 -08008667 TextDirectionHeuristics.FIRSTSTRONG_LTR);
Gilles Debunne60e21862012-01-30 15:04:14 -08008668 case TEXT_DIRECTION_ANY_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008669 return TextDirectionHeuristics.ANYRTL_LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008670 case TEXT_DIRECTION_LTR:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008671 return TextDirectionHeuristics.LTR;
Gilles Debunne60e21862012-01-30 15:04:14 -08008672 case TEXT_DIRECTION_RTL:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008673 return TextDirectionHeuristics.RTL;
Gilles Debunne60e21862012-01-30 15:04:14 -08008674 case TEXT_DIRECTION_LOCALE:
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008675 return TextDirectionHeuristics.LOCALE;
Gilles Debunne60e21862012-01-30 15:04:14 -08008676 }
8677 }
8678
Fabrice Di Meglio4457e852012-09-18 19:23:12 -07008679 /**
8680 * @hide
8681 */
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008682 @Override
8683 public void onResolveDrawables(int layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008684 // No need to resolve twice
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008685 if (mLastLayoutDirection == layoutDirection) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008686 return;
8687 }
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008688 mLastLayoutDirection = layoutDirection;
Gilles Debunne60e21862012-01-30 15:04:14 -08008689
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008690 // Resolve drawables
8691 if (mDrawables != null) {
8692 mDrawables.resolveWithLayoutDirection(layoutDirection);
Fabrice Di Megliob03b4342012-06-04 12:55:30 -07008693 }
8694 }
8695
Fabrice Di Meglio84ebb352012-10-11 16:27:37 -07008696 /**
8697 * @hide
8698 */
Gilles Debunne60e21862012-01-30 15:04:14 -08008699 protected void resetResolvedDrawables() {
Fabrice Di Megliobb0cbae2012-11-13 20:51:24 -08008700 super.resetResolvedDrawables();
Fabrice Di Meglio1957d282012-10-25 17:42:39 -07008701 mLastLayoutDirection = -1;
Gilles Debunne60e21862012-01-30 15:04:14 -08008702 }
8703
8704 /**
8705 * @hide
8706 */
8707 protected void viewClicked(InputMethodManager imm) {
8708 if (imm != null) {
8709 imm.viewClicked(this);
8710 }
8711 }
8712
8713 /**
8714 * Deletes the range of text [start, end[.
8715 * @hide
8716 */
8717 protected void deleteText_internal(int start, int end) {
8718 ((Editable) mText).delete(start, end);
8719 }
8720
8721 /**
8722 * Replaces the range of text [start, end[ by replacement text
8723 * @hide
8724 */
8725 protected void replaceText_internal(int start, int end, CharSequence text) {
8726 ((Editable) mText).replace(start, end, text);
8727 }
8728
8729 /**
8730 * Sets a span on the specified range of text
8731 * @hide
8732 */
8733 protected void setSpan_internal(Object span, int start, int end, int flags) {
8734 ((Editable) mText).setSpan(span, start, end, flags);
8735 }
8736
8737 /**
8738 * Moves the cursor to the specified offset position in text
8739 * @hide
8740 */
8741 protected void setCursorPosition_internal(int start, int end) {
8742 Selection.setSelection(((Editable) mText), start, end);
8743 }
8744
8745 /**
8746 * An Editor should be created as soon as any of the editable-specific fields (grouped
8747 * inside the Editor object) is assigned to a non-default value.
8748 * This method will create the Editor if needed.
8749 *
8750 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
8751 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
8752 * Editor for backward compatibility, as soon as one of these fields is assigned.
8753 *
8754 * Also note that for performance reasons, the mEditor is created when needed, but not
8755 * reset when no more edit-specific fields are needed.
8756 */
Gilles Debunne5fae9962012-05-08 14:53:20 -07008757 private void createEditorIfNeeded() {
Gilles Debunne60e21862012-01-30 15:04:14 -08008758 if (mEditor == null) {
Gilles Debunned88876a2012-03-16 17:34:04 -07008759 mEditor = new Editor(this);
Gilles Debunne60e21862012-01-30 15:04:14 -08008760 }
8761 }
8762
Gilles Debunne60e21862012-01-30 15:04:14 -08008763 /**
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008764 * @hide
8765 */
8766 @Override
8767 public CharSequence getIterableTextForAccessibility() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008768 if (!(mText instanceof Spannable)) {
8769 setText(mText, BufferType.SPANNABLE);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008770 }
Svetoslavdb7da0e2013-04-22 18:34:02 -07008771 return mText;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008772 }
8773
8774 /**
8775 * @hide
8776 */
8777 @Override
8778 public TextSegmentIterator getIteratorForGranularity(int granularity) {
8779 switch (granularity) {
8780 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
8781 Spannable text = (Spannable) getIterableTextForAccessibility();
8782 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8783 AccessibilityIterators.LineTextSegmentIterator iterator =
8784 AccessibilityIterators.LineTextSegmentIterator.getInstance();
8785 iterator.initialize(text, getLayout());
8786 return iterator;
8787 }
8788 } break;
8789 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
8790 Spannable text = (Spannable) getIterableTextForAccessibility();
8791 if (!TextUtils.isEmpty(text) && getLayout() != null) {
8792 AccessibilityIterators.PageTextSegmentIterator iterator =
8793 AccessibilityIterators.PageTextSegmentIterator.getInstance();
8794 iterator.initialize(this);
8795 return iterator;
8796 }
8797 } break;
8798 }
8799 return super.getIteratorForGranularity(granularity);
8800 }
8801
8802 /**
8803 * @hide
8804 */
8805 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008806 public int getAccessibilitySelectionStart() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008807 return getSelectionStart();
Svetoslav7c512842013-01-30 23:02:08 -08008808 }
8809
8810 /**
8811 * @hide
8812 */
8813 public boolean isAccessibilitySelectionExtendable() {
8814 return true;
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008815 }
8816
8817 /**
8818 * @hide
8819 */
8820 @Override
Svetoslav7c512842013-01-30 23:02:08 -08008821 public int getAccessibilitySelectionEnd() {
Svetoslavdb7da0e2013-04-22 18:34:02 -07008822 return getSelectionEnd();
Svetoslav7c512842013-01-30 23:02:08 -08008823 }
8824
8825 /**
8826 * @hide
8827 */
8828 @Override
8829 public void setAccessibilitySelection(int start, int end) {
8830 if (getAccessibilitySelectionStart() == start
8831 && getAccessibilitySelectionEnd() == end) {
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008832 return;
8833 }
Svetoslavabad55d2013-05-07 18:49:51 -07008834 // Hide all selection controllers used for adjusting selection
8835 // since we are doing so explicitlty by other means and these
8836 // controllers interact with how selection behaves.
8837 if (mEditor != null) {
8838 mEditor.hideControllers();
8839 }
Svetoslav7c512842013-01-30 23:02:08 -08008840 CharSequence text = getIterableTextForAccessibility();
Svetoslavabad55d2013-05-07 18:49:51 -07008841 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
Svetoslav7c512842013-01-30 23:02:08 -08008842 Selection.setSelection((Spannable) text, start, end);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008843 } else {
Svetoslav7c512842013-01-30 23:02:08 -08008844 Selection.removeSelection((Spannable) text);
Svetoslav Ganov6d17a932012-04-27 19:30:38 -07008845 }
8846 }
8847
8848 /**
Gilles Debunne60e21862012-01-30 15:04:14 -08008849 * User interface state that is stored by TextView for implementing
8850 * {@link View#onSaveInstanceState}.
8851 */
8852 public static class SavedState extends BaseSavedState {
8853 int selStart;
8854 int selEnd;
8855 CharSequence text;
8856 boolean frozenWithFocus;
8857 CharSequence error;
8858
8859 SavedState(Parcelable superState) {
8860 super(superState);
8861 }
8862
8863 @Override
8864 public void writeToParcel(Parcel out, int flags) {
8865 super.writeToParcel(out, flags);
8866 out.writeInt(selStart);
8867 out.writeInt(selEnd);
8868 out.writeInt(frozenWithFocus ? 1 : 0);
8869 TextUtils.writeToParcel(text, out, flags);
8870
8871 if (error == null) {
8872 out.writeInt(0);
8873 } else {
8874 out.writeInt(1);
8875 TextUtils.writeToParcel(error, out, flags);
8876 }
8877 }
8878
8879 @Override
8880 public String toString() {
8881 String str = "TextView.SavedState{"
8882 + Integer.toHexString(System.identityHashCode(this))
8883 + " start=" + selStart + " end=" + selEnd;
8884 if (text != null) {
8885 str += " text=" + text;
8886 }
8887 return str + "}";
8888 }
8889
8890 @SuppressWarnings("hiding")
8891 public static final Parcelable.Creator<SavedState> CREATOR
8892 = new Parcelable.Creator<SavedState>() {
8893 public SavedState createFromParcel(Parcel in) {
8894 return new SavedState(in);
8895 }
8896
8897 public SavedState[] newArray(int size) {
8898 return new SavedState[size];
8899 }
8900 };
8901
8902 private SavedState(Parcel in) {
8903 super(in);
8904 selStart = in.readInt();
8905 selEnd = in.readInt();
8906 frozenWithFocus = (in.readInt() != 0);
8907 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8908
8909 if (in.readInt() != 0) {
8910 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
8911 }
8912 }
8913 }
8914
8915 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
8916 private char[] mChars;
8917 private int mStart, mLength;
8918
8919 public CharWrapper(char[] chars, int start, int len) {
8920 mChars = chars;
8921 mStart = start;
8922 mLength = len;
8923 }
8924
8925 /* package */ void set(char[] chars, int start, int len) {
8926 mChars = chars;
8927 mStart = start;
8928 mLength = len;
8929 }
8930
8931 public int length() {
8932 return mLength;
8933 }
8934
8935 public char charAt(int off) {
8936 return mChars[off + mStart];
8937 }
8938
8939 @Override
8940 public String toString() {
8941 return new String(mChars, mStart, mLength);
8942 }
8943
8944 public CharSequence subSequence(int start, int end) {
8945 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8946 throw new IndexOutOfBoundsException(start + ", " + end);
8947 }
8948
8949 return new String(mChars, start + mStart, end - start);
8950 }
8951
8952 public void getChars(int start, int end, char[] buf, int off) {
8953 if (start < 0 || end < 0 || start > mLength || end > mLength) {
8954 throw new IndexOutOfBoundsException(start + ", " + end);
8955 }
8956
8957 System.arraycopy(mChars, start + mStart, buf, off, end - start);
8958 }
8959
8960 public void drawText(Canvas c, int start, int end,
8961 float x, float y, Paint p) {
8962 c.drawText(mChars, start + mStart, end - start, x, y, p);
8963 }
8964
8965 public void drawTextRun(Canvas c, int start, int end,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008966 int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
Gilles Debunne60e21862012-01-30 15:04:14 -08008967 int count = end - start;
8968 int contextCount = contextEnd - contextStart;
8969 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008970 contextCount, x, y, flags, p);
Gilles Debunne60e21862012-01-30 15:04:14 -08008971 }
8972
8973 public float measureText(int start, int end, Paint p) {
8974 return p.measureText(mChars, start + mStart, end - start);
8975 }
8976
8977 public int getTextWidths(int start, int end, float[] widths, Paint p) {
8978 return p.getTextWidths(mChars, start + mStart, end - start, widths);
8979 }
8980
8981 public float getTextRunAdvances(int start, int end, int contextStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008982 int contextEnd, int flags, float[] advances, int advancesIndex,
Gilles Debunne60e21862012-01-30 15:04:14 -08008983 Paint p) {
8984 int count = end - start;
8985 int contextCount = contextEnd - contextStart;
8986 return p.getTextRunAdvances(mChars, start + mStart, count,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008987 contextStart + mStart, contextCount, flags, advances,
Gilles Debunne60e21862012-01-30 15:04:14 -08008988 advancesIndex);
8989 }
8990
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008991 public int getTextRunCursor(int contextStart, int contextEnd, int flags,
Gilles Debunne60e21862012-01-30 15:04:14 -08008992 int offset, int cursorOpt, Paint p) {
8993 int contextCount = contextEnd - contextStart;
8994 return p.getTextRunCursor(mChars, contextStart + mStart,
Fabrice Di Meglioda12f382013-03-15 11:26:56 -07008995 contextCount, flags, offset + mStart, cursorOpt);
Gilles Debunne60e21862012-01-30 15:04:14 -08008996 }
8997 }
8998
Gilles Debunne60e21862012-01-30 15:04:14 -08008999 private static final class Marquee extends Handler {
9000 // TODO: Add an option to configure this
9001 private static final float MARQUEE_DELTA_MAX = 0.07f;
9002 private static final int MARQUEE_DELAY = 1200;
9003 private static final int MARQUEE_RESTART_DELAY = 1200;
9004 private static final int MARQUEE_RESOLUTION = 1000 / 30;
9005 private static final int MARQUEE_PIXELS_PER_SECOND = 30;
9006
9007 private static final byte MARQUEE_STOPPED = 0x0;
9008 private static final byte MARQUEE_STARTING = 0x1;
9009 private static final byte MARQUEE_RUNNING = 0x2;
9010
9011 private static final int MESSAGE_START = 0x1;
9012 private static final int MESSAGE_TICK = 0x2;
9013 private static final int MESSAGE_RESTART = 0x3;
9014
9015 private final WeakReference<TextView> mView;
9016
9017 private byte mStatus = MARQUEE_STOPPED;
9018 private final float mScrollUnit;
9019 private float mMaxScroll;
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009020 private float mMaxFadeScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009021 private float mGhostStart;
9022 private float mGhostOffset;
9023 private float mFadeStop;
9024 private int mRepeatLimit;
9025
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009026 private float mScroll;
Gilles Debunne60e21862012-01-30 15:04:14 -08009027
9028 Marquee(TextView v) {
9029 final float density = v.getContext().getResources().getDisplayMetrics().density;
9030 mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
9031 mView = new WeakReference<TextView>(v);
9032 }
9033
9034 @Override
9035 public void handleMessage(Message msg) {
9036 switch (msg.what) {
9037 case MESSAGE_START:
9038 mStatus = MARQUEE_RUNNING;
9039 tick();
9040 break;
9041 case MESSAGE_TICK:
9042 tick();
9043 break;
9044 case MESSAGE_RESTART:
9045 if (mStatus == MARQUEE_RUNNING) {
9046 if (mRepeatLimit >= 0) {
9047 mRepeatLimit--;
9048 }
9049 start(mRepeatLimit);
9050 }
9051 break;
9052 }
9053 }
9054
9055 void tick() {
9056 if (mStatus != MARQUEE_RUNNING) {
9057 return;
9058 }
9059
9060 removeMessages(MESSAGE_TICK);
9061
9062 final TextView textView = mView.get();
9063 if (textView != null && (textView.isFocused() || textView.isSelected())) {
9064 mScroll += mScrollUnit;
9065 if (mScroll > mMaxScroll) {
9066 mScroll = mMaxScroll;
9067 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
9068 } else {
9069 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
9070 }
9071 textView.invalidate();
9072 }
9073 }
9074
9075 void stop() {
9076 mStatus = MARQUEE_STOPPED;
9077 removeMessages(MESSAGE_START);
9078 removeMessages(MESSAGE_RESTART);
9079 removeMessages(MESSAGE_TICK);
9080 resetScroll();
9081 }
9082
9083 private void resetScroll() {
9084 mScroll = 0.0f;
9085 final TextView textView = mView.get();
9086 if (textView != null) textView.invalidate();
9087 }
9088
9089 void start(int repeatLimit) {
9090 if (repeatLimit == 0) {
9091 stop();
9092 return;
9093 }
9094 mRepeatLimit = repeatLimit;
9095 final TextView textView = mView.get();
9096 if (textView != null && textView.mLayout != null) {
9097 mStatus = MARQUEE_STARTING;
9098 mScroll = 0.0f;
9099 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9100 textView.getCompoundPaddingRight();
9101 final float lineWidth = textView.mLayout.getLineWidth(0);
9102 final float gap = textWidth / 3.0f;
9103 mGhostStart = lineWidth - textWidth + gap;
9104 mMaxScroll = mGhostStart + textWidth;
9105 mGhostOffset = lineWidth + gap;
9106 mFadeStop = lineWidth + textWidth / 6.0f;
9107 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9108
9109 textView.invalidate();
9110 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
9111 }
9112 }
9113
9114 float getGhostOffset() {
9115 return mGhostOffset;
9116 }
9117
Fabrice Di Meglio7d6f6c92012-07-25 15:34:00 -07009118 float getScroll() {
9119 return mScroll;
9120 }
9121
9122 float getMaxFadeScroll() {
9123 return mMaxFadeScroll;
9124 }
9125
Gilles Debunne60e21862012-01-30 15:04:14 -08009126 boolean shouldDrawLeftFade() {
9127 return mScroll <= mFadeStop;
9128 }
9129
9130 boolean shouldDrawGhost() {
9131 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9132 }
9133
9134 boolean isRunning() {
9135 return mStatus == MARQUEE_RUNNING;
9136 }
9137
9138 boolean isStopped() {
9139 return mStatus == MARQUEE_STOPPED;
9140 }
9141 }
9142
Gilles Debunne60e21862012-01-30 15:04:14 -08009143 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9144
9145 private CharSequence mBeforeText;
9146
Gilles Debunne60e21862012-01-30 15:04:14 -08009147 public void beforeTextChanged(CharSequence buffer, int start,
9148 int before, int after) {
9149 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9150 + " before=" + before + " after=" + after + ": " + buffer);
9151
9152 if (AccessibilityManager.getInstance(mContext).isEnabled()
Svetoslav Ganov72bba582012-11-05 13:53:43 -08009153 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9154 || shouldSpeakPasswordsForAccessibility())) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009155 mBeforeText = buffer.toString();
9156 }
9157
9158 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9159 }
9160
Gilles Debunned88876a2012-03-16 17:34:04 -07009161 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009162 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9163 + " before=" + before + " after=" + after + ": " + buffer);
9164 TextView.this.handleTextChanged(buffer, start, before, after);
9165
Gilles Debunne60e21862012-01-30 15:04:14 -08009166 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9167 (isFocused() || isSelected() && isShown())) {
9168 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9169 mBeforeText = null;
9170 }
9171 }
9172
9173 public void afterTextChanged(Editable buffer) {
9174 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9175 TextView.this.sendAfterTextChanged(buffer);
9176
9177 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9178 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9179 }
9180 }
9181
Gilles Debunned88876a2012-03-16 17:34:04 -07009182 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
Gilles Debunne60e21862012-01-30 15:04:14 -08009183 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9184 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9185 TextView.this.spanChange(buf, what, s, st, e, en);
9186 }
9187
9188 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9189 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9190 + " what=" + what + ": " + buf);
9191 TextView.this.spanChange(buf, what, -1, s, -1, e);
9192 }
9193
9194 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9195 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9196 + " what=" + what + ": " + buf);
9197 TextView.this.spanChange(buf, what, s, -1, e, -1);
9198 }
satoka67a3cf2011-09-07 17:14:03 +09009199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08009200}